From 94038d329705acb79a0f804458821d43f59121db Mon Sep 17 00:00:00 2001 From: Drew Powers <1369770+drwpow@users.noreply.github.com> Date: Mon, 3 May 2021 12:26:10 -0600 Subject: [PATCH] Format (#167) --- .changeset/config.json | 5 +- .prettierignore | 1 + .vscode/launch.json | 62 +- .vscode/tasks.json | 29 +- README.md | 7 +- docs/cli.md | 2 +- docs/dev.md | 9 +- docs/syntax.md | 33 +- .../src/components/PreactCounter.tsx | 20 +- .../src/components/ReactCounter.jsx | 20 +- examples/snowpack/README.md | 26 +- .../snowpack/src/components/CompanyLogo.jsx | 2 +- .../src/components/PluginSearchPage.jsx | 6 +- examples/snowpack/src/components/docsearch.js | 31 +- examples/snowpack/src/data/version.js | 4 +- .../snowpack/src/pages/guides/web-worker.md | 2 +- lerna.json | 5 +- package.json | 4 +- packages/astro/internal.d.ts | 18 +- packages/astro/src/build.ts | 5 +- packages/astro/src/cli.ts | 4 +- packages/astro/src/compiler/index.ts | 2 +- packages/astro/src/config.ts | 4 +- packages/astro/src/dev.ts | 2 +- packages/astro/src/frontend/render/utils.ts | 2 +- packages/astro/src/logger.ts | 10 +- packages/astro/src/runtime.ts | 4 +- packages/astro/src/search.ts | 6 +- packages/astro/test/astro-doctype.test.js | 1 - packages/astro/test/config-path.test.js | 2 +- packages/astro/test/config-port.test.js | 2 +- .../astro-basic/src/pages/nested-md/index.md | 2 +- .../src/components/Component.jsx | 2 +- .../astro-dynamic/src/components/Counter.jsx | 6 +- .../astro-expr/src/components/Color.jsx | 6 +- .../astro-fallback/src/components/Client.jsx | 8 +- .../astro-markdown/src/components/Example.jsx | 6 +- .../astro-markdown/src/components/Hello.jsx | 6 +- .../astro-markdown/src/pages/complex.md | 2 +- packages/astro/test/test-utils.js | 1 - packages/create-astro/CHANGELOG.md | 3 +- packages/create-astro/README.md | 3 + packages/create-astro/src/components/App.tsx | 149 +- .../create-astro/src/components/Confirm.tsx | 2 +- .../create-astro/src/components/Emoji.tsx | 2 +- packages/create-astro/src/components/Exit.tsx | 11 +- .../create-astro/src/components/Finalize.tsx | 19 +- .../create-astro/src/components/Header.tsx | 34 +- packages/create-astro/src/components/Help.tsx | 124 +- .../create-astro/src/components/Install.tsx | 12 +- .../src/components/ProjectName.tsx | 8 +- .../create-astro/src/components/Select.tsx | 34 +- .../create-astro/src/components/Spacer.tsx | 2 +- .../create-astro/src/components/Spinner.tsx | 170 +- .../create-astro/src/components/Template.tsx | 2 +- packages/create-astro/src/config.ts | 78 +- packages/create-astro/src/index.tsx | 62 +- .../src/templates/blank/meta.json | 4 +- .../src/templates/starter/README.md | 2 - .../src/templates/starter/meta.json | 6 +- packages/create-astro/src/utils.ts | 93 +- scripts/package.json | 28 +- scripts/utils/svelte-plugin.js | 89 +- tools/prettier-plugin-astro/index.js | 56 +- .../test/astro-prettier.test.js | 4 +- tools/prettier-plugin-astro/test/package.json | 2 +- .../astro-language-configuration.json | 68 +- tools/vscode/package.json | 188 +-- tools/vscode/packages/client/package.json | 20 +- .../packages/client/src/html/autoClose.ts | 155 +- tools/vscode/packages/client/tsconfig.json | 18 +- .../server/src/core/config/ConfigManager.ts | 16 +- .../server/src/core/documents/Document.ts | 260 ++- .../src/core/documents/DocumentManager.ts | 146 +- .../server/src/core/documents/parseAstro.ts | 99 +- .../server/src/core/documents/parseHtml.ts | 238 ++- .../server/src/core/documents/utils.ts | 146 +- tools/vscode/packages/server/src/index.ts | 10 +- .../packages/server/src/plugins/PluginHost.ts | 240 ++- .../server/src/plugins/astro/AstroPlugin.ts | 2 +- .../server/src/plugins/html/HTMLPlugin.ts | 13 +- .../packages/server/src/plugins/interfaces.ts | 206 +-- .../typescript/LanguageServiceManager.ts | 2 +- .../src/plugins/typescript/SnapshotManager.ts | 518 +++--- .../plugins/typescript/TypeScriptPlugin.ts | 110 +- .../src/plugins/typescript/astro-sys.ts | 62 +- .../features/CompletionsProvider.ts | 2 +- .../src/plugins/typescript/languageService.ts | 59 +- .../server/src/plugins/typescript/utils.ts | 264 ++-- tools/vscode/packages/server/src/utils.ts | 83 +- tools/vscode/packages/server/tsconfig.json | 14 +- tools/vscode/syntaxes/astro.tmLanguage.json | 1406 +++++++++-------- tools/vscode/tsconfig.base.json | 31 +- tools/vscode/tsconfig.json | 23 +- www/snowpack.config.js | 5 +- yarn.lock | 8 +- 96 files changed, 2777 insertions(+), 3003 deletions(-) create mode 100644 .prettierignore diff --git a/.changeset/config.json b/.changeset/config.json index 26edf6fe8..51e0c0af5 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -6,8 +6,5 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": [ - "@example/*", - "www" - ] + "ignore": ["@example/*", "www"] } diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..752101540 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +**/dist/* diff --git a/.vscode/launch.json b/.vscode/launch.json index 35e13d1aa..b458758c8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,36 +1,32 @@ // A launch configuration that compiles the extension and then opens it inside a new window { - "version": "0.2.0", - "configurations": [ - { - "type": "extensionHost", - "request": "launch", - "name": "Launch Client", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceRoot}/vscode" - ], - "outFiles": [ - "${workspaceRoot}/vscode/dist/**/*.js" - ], - "preLaunchTask": { - "type": "npm", - "script": "build:extension" - } - }, - { - "type": "node", - "request": "attach", - "name": "Attach to Server", - "port": 6040, - "restart": true, - "outFiles": ["${workspaceRoot}/vscode/dist/**/*.js"] - }, - ], - "compounds": [ - { - "name": "Launch Extension", - "configurations": ["Launch Client", "Attach to Server"] - } - ] + "version": "0.2.0", + "configurations": [ + { + "type": "extensionHost", + "request": "launch", + "name": "Launch Client", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}/vscode"], + "outFiles": ["${workspaceRoot}/vscode/dist/**/*.js"], + "preLaunchTask": { + "type": "npm", + "script": "build:extension" + } + }, + { + "type": "node", + "request": "attach", + "name": "Attach to Server", + "port": 6040, + "restart": true, + "outFiles": ["${workspaceRoot}/vscode/dist/**/*.js"] + } + ], + "compounds": [ + { + "name": "Launch Extension", + "configurations": ["Launch Client", "Attach to Server"] + } + ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 073d95220..3b066f021 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,18 +1,15 @@ - { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "build:extension", - "group": "build", - "presentation": { - "panel": "dedicated", - "reveal": "never" - }, - "problemMatcher": [ - "$tsc" - ] - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build:extension", + "group": "build", + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": ["$tsc"] + } + ] } diff --git a/README.md b/README.md index a89ed9a70..0311c26b9 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ npm run build To deploy your Astro site to production, upload the contents of `/dist` to your favorite static site host. - ## 🥾 Guides ### 🚀 Basic Usage @@ -66,8 +65,8 @@ Even though nearly-everything [is configurable][docs-config], we recommend start Routing happens in `src/pages/*`. Every `.astro` or `.md.astro` file in this folder corresponds with a public URL. For example: -| Local file | Public URL | -| :--------------------------------------- | :------------------------------ | +| Local file | Public URL | +| :------------------------------------- | :------------------------------ | | `src/pages/index.astro` | `/index.html` | | `src/pages/post/my-blog-post.md.astro` | `/post/my-blog-post/index.html` | @@ -164,10 +163,10 @@ Astro will automatically create a `/sitemap.xml` for you for SEO! Be sure to set 👉 [**Collections API**][docs-collections] - ## ⚙️ Config 👉 [**`astro.config.mjs` Reference**][docs-config] + ## 📚 API 👉 [**Full API Reference**][docs-api] diff --git a/docs/cli.md b/docs/cli.md index 592cf1635..2ef0592a5 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -36,7 +36,7 @@ Runs the Astro development server. This starts an HTTP server that responds to r See the [dev server](./dev.md) docs for more information on how the dev server works. -__Flags__ +**Flags** ##### `--port` diff --git a/docs/dev.md b/docs/dev.md index 15687d1bb..d9223cbbd 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -14,7 +14,7 @@ The dev server will serve the following special routes: ### /400 -This is a custom __400__ status code page. You can add this route by adding a page component to your `src/pages` folder: +This is a custom **400** status code page. You can add this route by adding a page component to your `src/pages` folder: ``` ├── src/ @@ -27,13 +27,10 @@ For any URL you visit that doesn't have a corresponding page, the `400.astro` fi ### /500 -This is a custom __500__ status code page. You can add this route by adding a page component to your `src/pages` folder: +This is a custom **500** status code page. You can add this route by adding a page component to your `src/pages` folder: ```astro -├── src/ -│ ├── components/ -│ └── pages/ -│ └── 500.astro +├── src/ │ ├── components/ │ └── pages/ │ └── 500.astro ``` This page is used any time an error occurs in the dev server. diff --git a/docs/syntax.md b/docs/syntax.md index 21f964569..505abeb9b 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -116,26 +116,25 @@ export let name; `.astro` files can end up looking very similar to `.jsx` files, but there are a few key differences. Here's a comparison between the two formats. -| Feature | Astro | JSX | -|------------------------- |------------------------------------------ |---------------------------------------------------- | -| File extension | `.astro` | `.jsx` or `.tsx` | -| User-Defined Components | `` | `` | -| Expression Syntax | `{}` | `{}` | -| Spread Attributes | `{...props}` | `{...props}` | -| Boolean Attributes | `autocomplete` === `autocomplete={true}` | `autocomplete` === `autocomplete={true}` | -| Inline Functions | `{items.map(item =>
  • {item}
  • )}` | `{items.map(item =>
  • {item}
  • )}` | -| IDE Support | WIP - [VS Code][code-ext] | Phenomenal | -| Requires JS import | No | Yes, `jsxPragma` (`React` or `h`) must be in scope | -| Fragments | Automatic | Wrap with `` or `<>` | -| Multiple frameworks per-file | Yes | No | -| Modifying `` | Just use `` | Per-framework (``, ``, etc) | -| Comment Style | `` | `{/* JavaScript */}` | -| Special Characters | ` ` | `{'\xa0'}` or `{String.fromCharCode(160)}` | -| Attributes | `dash-case` | `camelCase` | +| Feature | Astro | JSX | +| ---------------------------- | ---------------------------------------- | -------------------------------------------------- | +| File extension | `.astro` | `.jsx` or `.tsx` | +| User-Defined Components | `` | `` | +| Expression Syntax | `{}` | `{}` | +| Spread Attributes | `{...props}` | `{...props}` | +| Boolean Attributes | `autocomplete` === `autocomplete={true}` | `autocomplete` === `autocomplete={true}` | +| Inline Functions | `{items.map(item =>
  • {item}
  • )}` | `{items.map(item =>
  • {item}
  • )}` | +| IDE Support | WIP - [VS Code][code-ext] | Phenomenal | +| Requires JS import | No | Yes, `jsxPragma` (`React` or `h`) must be in scope | +| Fragments | Automatic | Wrap with `` or `<>` | +| Multiple frameworks per-file | Yes | No | +| Modifying `` | Just use `` | Per-framework (``, ``, etc) | +| Comment Style | `` | `{/* JavaScript */}` | +| Special Characters | ` ` | `{'\xa0'}` or `{String.fromCharCode(160)}` | +| Attributes | `dash-case` | `camelCase` | ### TODO: Styling ### TODO: Composition (Slots) - [code-ext]: https://marketplace.visualstudio.com/items?itemName=astro-build.astro diff --git a/examples/kitchen-sink/src/components/PreactCounter.tsx b/examples/kitchen-sink/src/components/PreactCounter.tsx index 9f1e8d887..e3761643f 100644 --- a/examples/kitchen-sink/src/components/PreactCounter.tsx +++ b/examples/kitchen-sink/src/components/PreactCounter.tsx @@ -3,18 +3,18 @@ import { useState } from 'preact/hooks'; /** a counter written in Preact */ export default function PreactCounter({ children }) { - const [count, setCount] = useState(0) - const add = () => setCount(i => i + 1); - const subtract = () => setCount(i => i - 1); + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); - return <> -
    + return ( + <> +
    {count}
    -
    -
    - {children} -
    - +
    +
    {children}
    + + ); } diff --git a/examples/kitchen-sink/src/components/ReactCounter.jsx b/examples/kitchen-sink/src/components/ReactCounter.jsx index 384dd9918..92871a8d8 100644 --- a/examples/kitchen-sink/src/components/ReactCounter.jsx +++ b/examples/kitchen-sink/src/components/ReactCounter.jsx @@ -2,18 +2,18 @@ import React, { useState } from 'react'; /** a counter written in React */ export default function ReactCounter({ children }) { - const [count, setCount] = useState(0) - const add = () => setCount(i => i + 1); - const subtract = () => setCount(i => i - 1); + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); - return <> -
    + return ( + <> +
    {count}
    -
    -
    - {children} -
    - +
    +
    {children}
    + + ); } diff --git a/examples/snowpack/README.md b/examples/snowpack/README.md index 11e615e1a..c3a71d6f8 100644 --- a/examples/snowpack/README.md +++ b/examples/snowpack/README.md @@ -1,21 +1,21 @@ # Astro Demo -## Getting setup +## Getting set up -1. Checkout Astro at: https://github.com/snowpackjs/astro - - 1. Install and build Astro: +1. Check out Astro at: https://github.com/snowpackjs/astro - ```shell - npm install - npm run build - ``` + 1. Install and build Astro: - 2. Link Astro: + ```shell + npm install + npm run build + ``` - ```shell - npm link - ``` + 2. Link Astro: + + ```shell + npm link + ``` 2. In this project link Astro and install other deps: @@ -34,4 +34,4 @@ npm run start ```shell npm run build -``` \ No newline at end of file +``` diff --git a/examples/snowpack/src/components/CompanyLogo.jsx b/examples/snowpack/src/components/CompanyLogo.jsx index 98be3f2eb..fc652f864 100644 --- a/examples/snowpack/src/components/CompanyLogo.jsx +++ b/examples/snowpack/src/components/CompanyLogo.jsx @@ -1,4 +1,4 @@ -import {h} from 'preact'; +import { h } from 'preact'; export default function CompanyLogo({ user }) { return ( diff --git a/examples/snowpack/src/components/PluginSearchPage.jsx b/examples/snowpack/src/components/PluginSearchPage.jsx index 51c7e6b0f..4ed46cb74 100644 --- a/examples/snowpack/src/components/PluginSearchPage.jsx +++ b/examples/snowpack/src/components/PluginSearchPage.jsx @@ -117,5 +117,9 @@ function PluginSearchPageLive() { } export default function PluginSearchPage(props) { - return import.meta.env.astro ?
    Loading...
    : + return import.meta.env.astro ? ( +
    Loading...
    + ) : ( + + ); } diff --git a/examples/snowpack/src/components/docsearch.js b/examples/snowpack/src/components/docsearch.js index d7ae95f30..f2b9e6441 100644 --- a/examples/snowpack/src/components/docsearch.js +++ b/examples/snowpack/src/components/docsearch.js @@ -1,17 +1,20 @@ import docsearch from 'docsearch.js/dist/cdn/docsearch.min.js'; -customElements.define('doc-search', class extends HTMLElement { - connectedCallback() { - if(!this._setup) { - const apiKey = this.getAttribute('api-key'); - const selector = this.getAttribute('selector'); - docsearch({ - apiKey: apiKey, - indexName: 'snowpack', - inputSelector: selector, - debug: true // Set debug to true if you want to inspect the dropdown - }); - this._setup = true; +customElements.define( + 'doc-search', + class extends HTMLElement { + connectedCallback() { + if (!this._setup) { + const apiKey = this.getAttribute('api-key'); + const selector = this.getAttribute('selector'); + docsearch({ + apiKey: apiKey, + indexName: 'snowpack', + inputSelector: selector, + debug: true, // Set debug to true if you want to inspect the dropdown + }); + this._setup = true; + } } - } -}); \ No newline at end of file + }, +); diff --git a/examples/snowpack/src/data/version.js b/examples/snowpack/src/data/version.js index 19a63491c..53f8ca53e 100644 --- a/examples/snowpack/src/data/version.js +++ b/examples/snowpack/src/data/version.js @@ -1,2 +1,4 @@ -const snowpackManifest = JSON.parse(fs.readFileSync(path.join(__dirname, '../../snowpack/package.json'), 'utf8')); +const snowpackManifest = JSON.parse( + fs.readFileSync(path.join(__dirname, '../../snowpack/package.json'), 'utf8'), +); export default snowpackManifest.version; diff --git a/examples/snowpack/src/pages/guides/web-worker.md b/examples/snowpack/src/pages/guides/web-worker.md index 4329c489a..56bce9774 100644 --- a/examples/snowpack/src/pages/guides/web-worker.md +++ b/examples/snowpack/src/pages/guides/web-worker.md @@ -28,4 +28,4 @@ const worker = new Worker(new URL('./esm-worker.js', import.meta.url), { name: 'my-worker', type: 'module', }); -``` \ No newline at end of file +``` diff --git a/lerna.json b/lerna.json index 6b98ebda7..ae71dbae7 100644 --- a/lerna.json +++ b/lerna.json @@ -1,8 +1,5 @@ { - "ignoreChanges": [ - "**/test/**", - "**/*.md" - ], + "ignoreChanges": ["**/test/**", "**/*.md"], "useWorkspaces": true, "version": "4.0.0" } diff --git a/package.json b/package.json index b7c9232a7..0fa802270 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "release": "yarn build && yarn changeset publish", "build": "yarn build:core", "build:core": "lerna run build --scope astro --scope astro-parser --scope create-astro", + "format": "prettier -w '**/*.{js,jsx,ts,tsx,md,json}'", "lint": "eslint 'packages/**/*.ts'", "test": "yarn test:core && yarn test:prettier", "test:core": "cd packages/astro && npm test", - "test:prettier": "cd tools/prettier-plugin-astro && npm test", - "format": "prettier -w '**/*.{js,jsx,ts,tsx,md,json}'" + "test:prettier": "cd tools/prettier-plugin-astro && npm test" }, "workspaces": [ "packages/*", diff --git a/packages/astro/internal.d.ts b/packages/astro/internal.d.ts index 5d0f0d123..40b65ffb3 100644 --- a/packages/astro/internal.d.ts +++ b/packages/astro/internal.d.ts @@ -1,27 +1,27 @@ declare module '#astro/compiler' { - export * from 'astro/dist/types/compiler'; + export * from 'astro/dist/types/compiler'; } declare module '#astro/ast' { - export * from 'astro/dist/types/ast'; + export * from 'astro/dist/types/ast'; } declare module '#astro/build' { - export * from 'astro/dist/types/build'; + export * from 'astro/dist/types/build'; } declare module '#astro/cli' { - export * from 'astro/dist/types/cli'; + export * from 'astro/dist/types/cli'; } declare module '#astro/config' { - export * from 'astro/dist/types/config'; + export * from 'astro/dist/types/config'; } declare module '#astro/dev' { - export * from 'astro/dist/types/dev'; + export * from 'astro/dist/types/dev'; } declare module '#astro/logger' { - export * from 'astro/dist/types/logger'; + export * from 'astro/dist/types/logger'; } declare module '#astro/runtime' { - export * from 'astro/dist/types/runtime'; + export * from 'astro/dist/types/runtime'; } declare module '#astro/search' { - export * from 'astro/dist/types/search'; + export * from 'astro/dist/types/search'; } diff --git a/packages/astro/src/build.ts b/packages/astro/src/build.ts index fb603641d..b7cca32e5 100644 --- a/packages/astro/src/build.ts +++ b/packages/astro/src/build.ts @@ -16,7 +16,6 @@ import { generateRSS } from './build/rss.js'; import { generateSitemap } from './build/sitemap.js'; import { collectStatics } from './build/static.js'; import { canonicalURL } from './build/util.js'; -import { pathToFileURL } from 'node:url'; const { mkdir, readFile, writeFile } = fsPromises; @@ -69,8 +68,8 @@ async function writeFilep(outPath: URL, bytes: string | Buffer, encoding: 'utf8' interface WriteResultOptions { srcPath: string; result: LoadResult; - outPath: URL, - encoding: null|'utf8' + outPath: URL; + encoding: null | 'utf8'; } /** Utility for writing a build result to disk */ diff --git a/packages/astro/src/cli.ts b/packages/astro/src/cli.ts index b028d754c..16f67fd60 100644 --- a/packages/astro/src/cli.ts +++ b/packages/astro/src/cli.ts @@ -31,10 +31,10 @@ interface CLIState { /** Determine which action the user requested */ function resolveArgs(flags: Arguments): CLIState { const options: CLIState['options'] = { - projectRoot: typeof flags.projectRoot === 'string' ? flags.projectRoot: undefined, + projectRoot: typeof flags.projectRoot === 'string' ? flags.projectRoot : undefined, sitemap: typeof flags.sitemap === 'boolean' ? flags.sitemap : undefined, port: typeof flags.port === 'number' ? flags.port : undefined, - config: typeof flags.config === 'string' ? flags.config : undefined + config: typeof flags.config === 'string' ? flags.config : undefined, }; if (flags.version) { diff --git a/packages/astro/src/compiler/index.ts b/packages/astro/src/compiler/index.ts index 6a9e8dbe6..a1a9bde36 100644 --- a/packages/astro/src/compiler/index.ts +++ b/packages/astro/src/compiler/index.ts @@ -15,7 +15,7 @@ import { encodeAstroMdx } from './markdown/micromark-mdx-astro.js'; import { transform } from './transform/index.js'; import { codegen } from './codegen/index.js'; -export { scopeRule } from './transform/postcss-scoped-styles/index.js' +export { scopeRule } from './transform/postcss-scoped-styles/index.js'; /** Return Astro internal import URL */ function internalImport(internalPath: string) { diff --git a/packages/astro/src/config.ts b/packages/astro/src/config.ts index cf4b5b229..d774d6b9e 100644 --- a/packages/astro/src/config.ts +++ b/packages/astro/src/config.ts @@ -26,8 +26,8 @@ function validateConfig(config: any): void { } } - if(typeof config.devOptions?.port !== 'number') { - throw new Error(`[astro config] devOptions.port: Expected number, received ${type(config.devOptions?.port)}`) + if (typeof config.devOptions?.port !== 'number') { + throw new Error(`[astro config] devOptions.port: Expected number, received ${type(config.devOptions?.port)}`); } } diff --git a/packages/astro/src/dev.ts b/packages/astro/src/dev.ts index 2522ab645..7aa4ba07d 100644 --- a/packages/astro/src/dev.ts +++ b/packages/astro/src/dev.ts @@ -75,7 +75,7 @@ export default async function dev(astroConfig: AstroConfig) { res.statusCode = 500; let errorResult = await runtime.load(`/500?error=${encodeURIComponent(result.error.stack || result.error.toString())}`); - if(errorResult.statusCode === 200) { + if (errorResult.statusCode === 200) { if (errorResult.contentType) { res.setHeader('Content-Type', errorResult.contentType); } diff --git a/packages/astro/src/frontend/render/utils.ts b/packages/astro/src/frontend/render/utils.ts index 5d13ca136..2dddf083e 100644 --- a/packages/astro/src/frontend/render/utils.ts +++ b/packages/astro/src/frontend/render/utils.ts @@ -49,6 +49,6 @@ export const childrenToH = moize.deep(function childrenToH(renderer: ComponentRe }; return tree.map((subtree) => { if (subtree.type === 'text') return JSON.stringify(subtree.value); - return toH(innerH, subtree).__SERIALIZED + return toH(innerH, subtree).__SERIALIZED; }); }); diff --git a/packages/astro/src/logger.ts b/packages/astro/src/logger.ts index f700e91e1..a97482459 100644 --- a/packages/astro/src/logger.ts +++ b/packages/astro/src/logger.ts @@ -16,13 +16,13 @@ export const defaultLogDestination = new Writable({ dest = process.stdout; } let type = event.type; - if(type !== null) { + if (type !== null) { if (event.level === 'info') { type = bold(blue(type)); } else if (event.level === 'error') { type = bold(red(type)); } - + dest.write(`[${type}] `); } @@ -135,10 +135,10 @@ export const logger = { }; // For silencing libraries that go directly to console.warn -export function trapWarn(cb: (...args: any[]) => void = () =>{}) { +export function trapWarn(cb: (...args: any[]) => void = () => {}) { const warn = console.warn; - console.warn = function(...args: any[]) { + console.warn = function (...args: any[]) { cb(...args); }; - return () => console.warn = warn; + return () => (console.warn = warn); } diff --git a/packages/astro/src/runtime.ts b/packages/astro/src/runtime.ts index d08c0f387..76e27516d 100644 --- a/packages/astro/src/runtime.ts +++ b/packages/astro/src/runtime.ts @@ -200,14 +200,14 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro // For first release query params are not passed to components. // An exception is made for dev server specific routes. - if(reqPath !== '/500') { + if (reqPath !== '/500') { requestURL.search = ''; } let html = (await mod.exports.__renderPage({ request: { // params should go here when implemented - url: requestURL + url: requestURL, }, children: [], props: { collection }, diff --git a/packages/astro/src/search.ts b/packages/astro/src/search.ts index f097751b1..96338dd0c 100644 --- a/packages/astro/src/search.ts +++ b/packages/astro/src/search.ts @@ -92,14 +92,14 @@ export function searchForPage(url: URL, astroRoot: URL): SearchResult { } } - if(reqPath === '/500') { + if (reqPath === '/500') { return { statusCode: 200, location: { fileURL: new URL('./frontend/500.astro', import.meta.url), - snowpackURL: `/_astro_internal/500.astro.js` + snowpackURL: `/_astro_internal/500.astro.js`, }, - pathname: reqPath + pathname: reqPath, }; } diff --git a/packages/astro/test/astro-doctype.test.js b/packages/astro/test/astro-doctype.test.js index 91d422665..c1b94908b 100644 --- a/packages/astro/test/astro-doctype.test.js +++ b/packages/astro/test/astro-doctype.test.js @@ -4,7 +4,6 @@ import * as assert from 'uvu/assert'; import { loadConfig } from '#astro/config'; import { createRuntime } from '#astro/runtime'; - const DType = suite('doctype'); let runtime, setupError; diff --git a/packages/astro/test/config-path.test.js b/packages/astro/test/config-path.test.js index 33e2cf3b7..5d78e7027 100644 --- a/packages/astro/test/config-path.test.js +++ b/packages/astro/test/config-path.test.js @@ -12,7 +12,7 @@ ConfigPath('can be passed via --config', async (context) => { process.stdout.setEncoding('utf8'); for await (const chunk of process.stdout) { - if(/Server started/.test(chunk)) { + if (/Server started/.test(chunk)) { break; } } diff --git a/packages/astro/test/config-port.test.js b/packages/astro/test/config-port.test.js index d8e4fdf14..000c5575b 100644 --- a/packages/astro/test/config-port.test.js +++ b/packages/astro/test/config-port.test.js @@ -17,7 +17,7 @@ ConfigPort('can be specified via --port flag', async (context) => { process.stdout.setEncoding('utf8'); for await (const chunk of process.stdout) { - if(/Local:/.test(chunk)) { + if (/Local:/.test(chunk)) { assert.ok(/:3002/.test(chunk), 'Using the right port'); break; } diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/nested-md/index.md b/packages/astro/test/fixtures/astro-basic/src/pages/nested-md/index.md index 23374f9b8..611660c4c 100644 --- a/packages/astro/test/fixtures/astro-basic/src/pages/nested-md/index.md +++ b/packages/astro/test/fixtures/astro-basic/src/pages/nested-md/index.md @@ -3,4 +3,4 @@ layout: ../../layouts/base.astro title: My Page --- -Hello world \ No newline at end of file +Hello world diff --git a/packages/astro/test/fixtures/astro-children/src/components/Component.jsx b/packages/astro/test/fixtures/astro-children/src/components/Component.jsx index bf9280f86..f25176192 100644 --- a/packages/astro/test/fixtures/astro-children/src/components/Component.jsx +++ b/packages/astro/test/fixtures/astro-children/src/components/Component.jsx @@ -1,5 +1,5 @@ import { h } from 'preact'; export default function PreactComponent({ children }) { - return
    {children}
    + return
    {children}
    ; } diff --git a/packages/astro/test/fixtures/astro-dynamic/src/components/Counter.jsx b/packages/astro/test/fixtures/astro-dynamic/src/components/Counter.jsx index 31472c3ac..7a7d6ea1f 100644 --- a/packages/astro/test/fixtures/astro-dynamic/src/components/Counter.jsx +++ b/packages/astro/test/fixtures/astro-dynamic/src/components/Counter.jsx @@ -1,9 +1,9 @@ import React from 'react'; -export default function() { +export default function () { return (
    - ) -} \ No newline at end of file + ); +} diff --git a/packages/astro/test/fixtures/astro-expr/src/components/Color.jsx b/packages/astro/test/fixtures/astro-expr/src/components/Color.jsx index c2681cc9b..9f7f40733 100644 --- a/packages/astro/test/fixtures/astro-expr/src/components/Color.jsx +++ b/packages/astro/test/fixtures/astro-expr/src/components/Color.jsx @@ -1,5 +1,5 @@ import { h } from 'preact'; -export default function({ name }) { - return
    {name}
    -} \ No newline at end of file +export default function ({ name }) { + return
    {name}
    ; +} diff --git a/packages/astro/test/fixtures/astro-fallback/src/components/Client.jsx b/packages/astro/test/fixtures/astro-fallback/src/components/Client.jsx index d79536e27..4cc5ea252 100644 --- a/packages/astro/test/fixtures/astro-fallback/src/components/Client.jsx +++ b/packages/astro/test/fixtures/astro-fallback/src/components/Client.jsx @@ -1,7 +1,5 @@ import { h } from 'preact'; -export default function(props) { - return ( -
    {import.meta.env.astro ? 'static' : 'dynamic'}
    - ); -}; \ No newline at end of file +export default function (props) { + return
    {import.meta.env.astro ? 'static' : 'dynamic'}
    ; +} diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/Example.jsx b/packages/astro/test/fixtures/astro-markdown/src/components/Example.jsx index 57bde3a95..e1f67ee50 100644 --- a/packages/astro/test/fixtures/astro-markdown/src/components/Example.jsx +++ b/packages/astro/test/fixtures/astro-markdown/src/components/Example.jsx @@ -1,5 +1,5 @@ import { h } from 'preact'; -export default function() { - return
    Testing
    -} \ No newline at end of file +export default function () { + return
    Testing
    ; +} diff --git a/packages/astro/test/fixtures/astro-markdown/src/components/Hello.jsx b/packages/astro/test/fixtures/astro-markdown/src/components/Hello.jsx index 787ca587b..d30dec516 100644 --- a/packages/astro/test/fixtures/astro-markdown/src/components/Hello.jsx +++ b/packages/astro/test/fixtures/astro-markdown/src/components/Hello.jsx @@ -1,5 +1,5 @@ import { h } from 'preact'; -export default function({ name }) { - return
    Hello {name}
    -} \ No newline at end of file +export default function ({ name }) { + return
    Hello {name}
    ; +} diff --git a/packages/astro/test/fixtures/astro-markdown/src/pages/complex.md b/packages/astro/test/fixtures/astro-markdown/src/pages/complex.md index 6367948b9..f55a11ad6 100644 --- a/packages/astro/test/fixtures/astro-markdown/src/pages/complex.md +++ b/packages/astro/test/fixtures/astro-markdown/src/pages/complex.md @@ -10,4 +10,4 @@ import: ## Interesting Topic - \ No newline at end of file + diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 1afab6a20..b53ff2f0f 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -4,4 +4,3 @@ import cheerio from 'cheerio'; export function doc(html) { return cheerio.load(html); } - diff --git a/packages/create-astro/CHANGELOG.md b/packages/create-astro/CHANGELOG.md index 0029af7d4..59abb17c5 100644 --- a/packages/create-astro/CHANGELOG.md +++ b/packages/create-astro/CHANGELOG.md @@ -1,8 +1,9 @@ # create-astro ## 0.1.0 + ### Minor Changes - ed63132: Added **interactive mode** with a fesh new UI. - + Included a new **blank** starter to get up and running even faster. diff --git a/packages/create-astro/README.md b/packages/create-astro/README.md index cc7d1619f..ff0c9993b 100644 --- a/packages/create-astro/README.md +++ b/packages/create-astro/README.md @@ -1,12 +1,15 @@ # create-astro + ## Scaffolding for Astro projects **With NPM:** + ```bash npm init astro ``` **With Yarn:** + ```bash yarn create astro ``` diff --git a/packages/create-astro/src/components/App.tsx b/packages/create-astro/src/components/App.tsx index fd9192bb6..deb9b252a 100644 --- a/packages/create-astro/src/components/App.tsx +++ b/packages/create-astro/src/components/App.tsx @@ -1,4 +1,4 @@ -import React, {FC, useEffect} from 'react'; +import React, { FC, useEffect } from 'react'; import { prepareTemplate, isEmpty, emptyDir } from '../utils'; import Header from './Header'; import Install from './Install'; @@ -8,86 +8,91 @@ import Confirm from './Confirm'; import Finalize from './Finalize'; interface Context { - use: 'npm'|'yarn'; - run: boolean; - projectExists?: boolean; - force?: boolean; - projectName?: string; - template?: string; - templates: string[]; - ready?: boolean; + use: 'npm' | 'yarn'; + run: boolean; + projectExists?: boolean; + force?: boolean; + projectName?: string; + template?: string; + templates: string[]; + ready?: boolean; } const getStep = ({ projectName, projectExists: exists, template, force, ready }: Context) => { - switch (true) { - case !projectName: return { - key: 'projectName', - Component: ProjectName - }; - case projectName && exists === true && typeof force === 'undefined': return { - key: 'force', - Component: Confirm - } - case (exists === false || force) && !template: return { - key: 'template', - Component: Template - }; - case !ready: return { - key: 'install', - Component: Install - }; - default: return { - key: 'final', - Component: Finalize - } - } -} + switch (true) { + case !projectName: + return { + key: 'projectName', + Component: ProjectName, + }; + case projectName && exists === true && typeof force === 'undefined': + return { + key: 'force', + Component: Confirm, + }; + case (exists === false || force) && !template: + return { + key: 'template', + Component: Template, + }; + case !ready: + return { + key: 'install', + Component: Install, + }; + default: + return { + key: 'final', + Component: Finalize, + }; + } +}; const App: FC<{ context: Context }> = ({ context }) => { - const [state, setState] = React.useState(context); - const step = React.useRef(getStep(context)); - const onSubmit = (value: string|boolean) => { - const { key } = step.current; - const newState = { ...state, [key]: value }; - step.current = getStep(newState) - setState(newState) - } + const [state, setState] = React.useState(context); + const step = React.useRef(getStep(context)); + const onSubmit = (value: string | boolean) => { + const { key } = step.current; + const newState = { ...state, [key]: value }; + step.current = getStep(newState); + setState(newState); + }; - useEffect(() => { - let isSubscribed = true - if (state.projectName && typeof state.projectExists === 'undefined') { - const newState = { ...state, projectExists: !isEmpty(state.projectName) }; - step.current = getStep(newState) - if (isSubscribed) { - setState(newState); - } - } + useEffect(() => { + let isSubscribed = true; + if (state.projectName && typeof state.projectExists === 'undefined') { + const newState = { ...state, projectExists: !isEmpty(state.projectName) }; + step.current = getStep(newState); + if (isSubscribed) { + setState(newState); + } + } - if (state.projectName && (state.projectExists === false || state.force) && state.template) { - if (state.force) emptyDir(state.projectName); - prepareTemplate(context.use, state.template, state.projectName).then(() => { - if (isSubscribed) { - setState(v => { - const newState = {...v, ready: true }; - step.current = getStep(newState); - return newState; - }); - } - }); - } + if (state.projectName && (state.projectExists === false || state.force) && state.template) { + if (state.force) emptyDir(state.projectName); + prepareTemplate(context.use, state.template, state.projectName).then(() => { + if (isSubscribed) { + setState((v) => { + const newState = { ...v, ready: true }; + step.current = getStep(newState); + return newState; + }); + } + }); + } - return () => { - isSubscribed = false; - } - }, [state]); - const { Component } = step.current; + return () => { + isSubscribed = false; + }; + }, [state]); + const { Component } = step.current; - return ( - <> -
    - - - ) + return ( + <> +
    + + + ); }; export default App; diff --git a/packages/create-astro/src/components/Confirm.tsx b/packages/create-astro/src/components/Confirm.tsx index 1fd1aa862..84d83ed8f 100644 --- a/packages/create-astro/src/components/Confirm.tsx +++ b/packages/create-astro/src/components/Confirm.tsx @@ -31,7 +31,7 @@ const Confirm: FC<{ message?: any; context: any; onSubmit: (value: boolean) => v items={[ { value: false, - label: 'no' + label: 'no', }, { value: true, diff --git a/packages/create-astro/src/components/Emoji.tsx b/packages/create-astro/src/components/Emoji.tsx index 3af9ae508..4a294f5db 100644 --- a/packages/create-astro/src/components/Emoji.tsx +++ b/packages/create-astro/src/components/Emoji.tsx @@ -2,4 +2,4 @@ import React from 'react'; import { Text } from 'ink'; import { isWin } from '../utils'; -export default ({ children }) => isWin() ? null : {children} +export default ({ children }) => (isWin() ? null : {children}); diff --git a/packages/create-astro/src/components/Exit.tsx b/packages/create-astro/src/components/Exit.tsx index cc3096705..b108fb36f 100644 --- a/packages/create-astro/src/components/Exit.tsx +++ b/packages/create-astro/src/components/Exit.tsx @@ -2,8 +2,11 @@ import React, { FC } from 'react'; import { Box, Text } from 'ink'; import { isDone } from '../utils'; -const Exit: FC<{ didError?: boolean }> = ({ didError }) => isDone ? null : - [abort] - astro cancelled - +const Exit: FC<{ didError?: boolean }> = ({ didError }) => + isDone ? null : ( + + [abort] + astro cancelled + + ); export default Exit; diff --git a/packages/create-astro/src/components/Finalize.tsx b/packages/create-astro/src/components/Finalize.tsx index 8d2a2103a..933a8844e 100644 --- a/packages/create-astro/src/components/Finalize.tsx +++ b/packages/create-astro/src/components/Finalize.tsx @@ -7,21 +7,26 @@ const Finalize: FC<{ context: any }> = ({ context: { use, projectName } }) => { cancelProcessListeners(); process.exit(0); }, []); - - return <> + + return ( + <> {'[ yes ]'} - Project initialized at ./{projectName} + + {' '} + Project initialized at ./{projectName} + {'[ tip ]'} - Get started by running - cd ./{projectName} - {use} start + Get started by running + cd ./{projectName} + {use} start - ; + + ); }; export default Finalize; diff --git a/packages/create-astro/src/components/Header.tsx b/packages/create-astro/src/components/Header.tsx index 1d894a60e..0684d1b3c 100644 --- a/packages/create-astro/src/components/Header.tsx +++ b/packages/create-astro/src/components/Header.tsx @@ -2,19 +2,27 @@ import React from 'react'; import { Box, Text } from 'ink'; const getMessage = ({ projectName, template }) => { - switch (true) { - case !projectName: return Gathering mission details; - case !template: return Optimizing navigational system; - default: return {projectName} - } -} + switch (true) { + case !projectName: + return Gathering mission details; + case !template: + return Optimizing navigational system; + default: + return ( + + {' '} + {projectName}{' '} + + ); + } +}; const Header: React.FC<{ context: any }> = ({ context }) => ( - - {' astro '} - - {getMessage(context)} - - -) + + + {' astro '} + + {getMessage(context)} + +); export default Header; diff --git a/packages/create-astro/src/components/Help.tsx b/packages/create-astro/src/components/Help.tsx index 88fcf5633..5991664f1 100644 --- a/packages/create-astro/src/components/Help.tsx +++ b/packages/create-astro/src/components/Help.tsx @@ -2,61 +2,79 @@ import React, { FC } from 'react'; import { Box, Text } from 'ink'; import { ARGS, ARG } from '../config'; -const Type: FC<{ type: any, enum?: string[] }> = ({ type, enum: e }) => { - if (type === Boolean) { - return <> - true - | - false - - } - if (e?.length > 0) { - return <> - {e.map((item, i, { length: len}) => { - if (i !== len - 1) { - return - {item} - | - - } - - return {item} - })} - - } - - return string; -} - -const Command: FC<{ name: string, info: ARG }> = ({ name, info: { alias, description, type, enum: e } }) => { +const Type: FC<{ type: any; enum?: string[] }> = ({ type, enum: e }) => { + if (type === Boolean) { return ( - - - --{name}{alias && -{alias}} - - - - - - {description} - - + <> + true + | + false + ); -} - -const Help: FC<{ context: any }> = ({ context: { templates }}) => { + } + if (e?.length > 0) { return ( - <> - - {' astro '} - - help - - - - {Object.entries(ARGS).map(([name, info]) => value) } : info} /> )} - - - ) + <> + {e.map((item, i, { length: len }) => { + if (i !== len - 1) { + return ( + + {item} + | + + ); + } + + return ( + + {item} + + ); + })} + + ); + } + + return string; +}; + +const Command: FC<{ name: string; info: ARG }> = ({ name, info: { alias, description, type, enum: e } }) => { + return ( + + + --{name} + {alias && -{alias}} + + + + + + {description} + + + ); +}; + +const Help: FC<{ context: any }> = ({ context: { templates } }) => { + return ( + <> + + + {' astro '} + + + + {' '} + help{' '} + + + + + {Object.entries(ARGS).map(([name, info]) => ( + value) } : info} /> + ))} + + + ); }; export default Help; diff --git a/packages/create-astro/src/components/Install.tsx b/packages/create-astro/src/components/Install.tsx index d9d2bc9b6..776c0cc34 100644 --- a/packages/create-astro/src/components/Install.tsx +++ b/packages/create-astro/src/components/Install.tsx @@ -4,16 +4,20 @@ import Spacer from './Spacer'; import Spinner from './Spinner'; const Install: FC<{ context: any }> = ({ context: { use } }) => { - return <> + return ( + <> - + Initiating launch sequence... - (aka running {use === 'npm' ? 'npm install' : 'yarn'}) + + (aka running {use === 'npm' ? 'npm install' : 'yarn'}) + - ; + + ); }; export default Install; diff --git a/packages/create-astro/src/components/ProjectName.tsx b/packages/create-astro/src/components/ProjectName.tsx index 87b976494..d09a33d82 100644 --- a/packages/create-astro/src/components/ProjectName.tsx +++ b/packages/create-astro/src/components/ProjectName.tsx @@ -8,8 +8,9 @@ const { default: Input } = TextInput; const ProjectName: FC<{ onSubmit: (value: string) => void }> = ({ onSubmit }) => { const [value, setValue] = React.useState(''); const handleSubmit = (v: string) => onSubmit(v); - - return <> + + return ( + <> {'[query]'} What is your project name? @@ -18,7 +19,8 @@ const ProjectName: FC<{ onSubmit: (value: string) => void }> = ({ onSubmit }) => - ; + + ); }; export default ProjectName; diff --git a/packages/create-astro/src/components/Select.tsx b/packages/create-astro/src/components/Select.tsx index acf8eb29f..08d588f4f 100644 --- a/packages/create-astro/src/components/Select.tsx +++ b/packages/create-astro/src/components/Select.tsx @@ -5,28 +5,28 @@ import { Text, Box } from 'ink'; const { default: Select } = SelectInput; interface Props { - isSelected?: boolean; - label: string; - description?: string; + isSelected?: boolean; + label: string; + description?: string; } -const Indicator: FC = ({ isSelected }) => isSelected ? [ : -const Item: FC = ({isSelected = false, label, description }) => ( - - {label} - {isSelected && description && typeof description === 'string' && {description}} - {isSelected && description && typeof description !== 'string' && {description}} - +const Indicator: FC = ({ isSelected }) => (isSelected ? [ : ); +const Item: FC = ({ isSelected = false, label, description }) => ( + + + {label} + + {isSelected && description && typeof description === 'string' && {description}} + {isSelected && description && typeof description !== 'string' && {description}} + ); interface SelectProps { - items: { value: string|number|boolean, label: string, description?: any }[] - onSelect(value: string|number|boolean): void; + items: { value: string | number | boolean; label: string; description?: any }[]; + onSelect(value: string | number | boolean): void; } const CustomSelect: FC = ({ items, onSelect }) => { - const handleSelect = ({ value }) => onSelect(value); - return ( - ; +}; export default CustomSelect; diff --git a/packages/create-astro/src/components/Spacer.tsx b/packages/create-astro/src/components/Spacer.tsx index 1e4e14561..15ef71d7c 100644 --- a/packages/create-astro/src/components/Spacer.tsx +++ b/packages/create-astro/src/components/Spacer.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; import { Box } from 'ink'; -const Spacer: FC<{ width?: number }> = ({ width = 8 }) => +const Spacer: FC<{ width?: number }> = ({ width = 8 }) => ; export default Spacer; diff --git a/packages/create-astro/src/components/Spinner.tsx b/packages/create-astro/src/components/Spinner.tsx index 2d3335e1c..fa62b614f 100644 --- a/packages/create-astro/src/components/Spinner.tsx +++ b/packages/create-astro/src/components/Spinner.tsx @@ -6,14 +6,14 @@ const Spinner: FC<{ type?: keyof typeof spinners }> = ({ type = 'countdown' }) = const [i, setI] = useState(0); useEffect(() => { const _ = setInterval(() => { - setI(v => (v < frames.length - 1) ? v + 1 : 0) - }, interval) + setI((v) => (v < frames.length - 1 ? v + 1 : 0)); + }, interval); return () => clearInterval(_); - }, []) + }, []); - return frames[i] -} + return frames[i]; +}; const spinners = { countdown: { @@ -35,73 +35,73 @@ const spinners = { {' '} , - {' '} + {' '} , - {' '} - {' '} + + {' '} , - {' '} - {' '} - {' '} + + + {' '} , - {' '} - {' '} - {' '} - {' '} + + + + {' '} , - {' '} - {' '} - {' '} - {' '} - {' '} + + + + + {' '} , - - {' '} - {' '} - {' '} - {' '} - {' '} - {' '} - {' '} + + + + + + + + , - + {' '} - {' '} - {' '} - {' '} - {' '} - {' '} + + + + + , - + {' '} - {' '} - {' '} - {' '} - {' '} + + + + , {' '} - {' '} - {' '} - {' '} + + + , {' '} - {' '} - {' '} + + , {' '} - {' '} + , {' '} @@ -122,79 +122,79 @@ const spinners = { {' '} , - {' '} + {' '} , - {' '} - {' '} + + {' '} , - {' '} - {' '} - {' '} + + + {' '} , - {' '} - {' '} - {' '} - {' '} + + + + {' '} , - {' '} - {' '} - {' '} - {' '} - {' '} + + + + + {' '} , - {' '} - {' '} - {' '} - {' '} - {' '} - {' '} - {' '} + + + + + + + , {' '} - {' '} - {' '} - {' '} - {' '} - {' '} + + + + + , {' '} - {' '} - {' '} - {' '} - {' '} + + + + , {' '} - {' '} - {' '} - {' '} + + + , {' '} - {' '} - {' '} + + , {' '} - {' '} + , {' '} , - ] - } -} + ], + }, +}; export default Spinner; diff --git a/packages/create-astro/src/components/Template.tsx b/packages/create-astro/src/components/Template.tsx index 7fbab035d..23ff905f2 100644 --- a/packages/create-astro/src/components/Template.tsx +++ b/packages/create-astro/src/components/Template.tsx @@ -3,7 +3,7 @@ import { Box, Text } from 'ink'; import Spacer from './Spacer'; import Select from './Select'; -const Template: FC<{ context: any, onSubmit: (value: string) => void }> = ({ context: { templates }, onSubmit }) => { +const Template: FC<{ context: any; onSubmit: (value: string) => void }> = ({ context: { templates }, onSubmit }) => { const items = templates.map(({ title: label, ...rest }) => ({ ...rest, label })); return ( diff --git a/packages/create-astro/src/config.ts b/packages/create-astro/src/config.ts index 3d3c07912..8216fd1ab 100644 --- a/packages/create-astro/src/config.ts +++ b/packages/create-astro/src/config.ts @@ -1,49 +1,49 @@ import type * as arg from 'arg'; export interface ARG { - type: any; - description: string; - enum?: string[]; - alias?: string; + type: any; + description: string; + enum?: string[]; + alias?: string; } export const ARGS: Record = { - 'template': { - type: String, - description: 'specifies template to use' - }, - 'use': { - type: String, - enum: ['npm', 'yarn'], - description: 'specifies package manager to use' - }, - 'run': { - type: Boolean, - description: 'should dependencies be installed automatically?' - }, - 'force': { - type: Boolean, - alias: 'f', - description: 'should existing files be overwritten?' - }, - 'version': { - type: Boolean, - alias: 'v', - description: 'prints current version' - }, - 'help': { - type: Boolean, - alias: 'h', - description: 'prints this message' - } -} + template: { + type: String, + description: 'specifies template to use', + }, + use: { + type: String, + enum: ['npm', 'yarn'], + description: 'specifies package manager to use', + }, + run: { + type: Boolean, + description: 'should dependencies be installed automatically?', + }, + force: { + type: Boolean, + alias: 'f', + description: 'should existing files be overwritten?', + }, + version: { + type: Boolean, + alias: 'v', + description: 'prints current version', + }, + help: { + type: Boolean, + alias: 'h', + description: 'prints this message', + }, +}; export const args = Object.entries(ARGS).reduce((acc, [name, info]) => { - const key = `--${name}`; - const spec = { ...acc, [key]: info.type }; + const key = `--${name}`; + const spec = { ...acc, [key]: info.type }; - if (info.alias) { - spec[`-${info.alias}`] = key; - } - return spec + if (info.alias) { + spec[`-${info.alias}`] = key; + } + return spec; }, {} as arg.Spec); diff --git a/packages/create-astro/src/index.tsx b/packages/create-astro/src/index.tsx index 0927eaae9..9a510d7b7 100644 --- a/packages/create-astro/src/index.tsx +++ b/packages/create-astro/src/index.tsx @@ -3,7 +3,7 @@ import React from 'react'; import App from './components/App'; import Version from './components/Version'; import Exit from './components/Exit'; -import {render} from 'ink'; +import { render } from 'ink'; import { getTemplates, addProcessListeners } from './utils'; import { args as argsConfig } from './config'; import arg from 'arg'; @@ -11,36 +11,36 @@ import Help from './components/Help'; /** main `create-astro` CLI */ export default async function createAstro() { - const args = arg(argsConfig); - const projectName = args._[0]; - if (args['--version']) { - return render(); - } - const templates = await getTemplates(); - if (args['--help']) { - return render() - } + const args = arg(argsConfig); + const projectName = args._[0]; + if (args['--version']) { + return render(); + } + const templates = await getTemplates(); + if (args['--help']) { + return render(); + } - const pkgManager = /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm'; - const use = (args['--use'] ?? pkgManager) as 'npm'|'yarn'; - const template = args['--template']; - const force = args['--force']; - const run = args['--run'] ?? true; - - const app = render(); + const pkgManager = /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm'; + const use = (args['--use'] ?? pkgManager) as 'npm' | 'yarn'; + const template = args['--template']; + const force = args['--force']; + const run = args['--run'] ?? true; - const onError = () => { - if (app) app.clear(); - render(); - } - const onExit = () => { - if (app) app.clear(); - render(); - } - addProcessListeners([ - ['uncaughtException', onError], - ['exit', onExit], - ['SIGINT', onExit], - ['SIGTERM', onExit], - ]) + const app = render(); + + const onError = () => { + if (app) app.clear(); + render(); + }; + const onExit = () => { + if (app) app.clear(); + render(); + }; + addProcessListeners([ + ['uncaughtException', onError], + ['exit', onExit], + ['SIGINT', onExit], + ['SIGTERM', onExit], + ]); } diff --git a/packages/create-astro/src/templates/blank/meta.json b/packages/create-astro/src/templates/blank/meta.json index ca942e99f..41a259d53 100644 --- a/packages/create-astro/src/templates/blank/meta.json +++ b/packages/create-astro/src/templates/blank/meta.json @@ -1,4 +1,4 @@ { - "title": "Blank", - "description": "a bare-bones, ultra-minimal template" + "title": "Blank", + "description": "a bare-bones, ultra-minimal template" } diff --git a/packages/create-astro/src/templates/starter/README.md b/packages/create-astro/src/templates/starter/README.md index 59940918f..70ff237f6 100644 --- a/packages/create-astro/src/templates/starter/README.md +++ b/packages/create-astro/src/templates/starter/README.md @@ -20,10 +20,8 @@ Inside of your Astro project, you'll see the following folders and files: ``` Astro looks for `.astro` or `.md.astro` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. - There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. - Any static assets, like images, can be placed in the `public/` directory. diff --git a/packages/create-astro/src/templates/starter/meta.json b/packages/create-astro/src/templates/starter/meta.json index ba775af32..e9f3778c7 100644 --- a/packages/create-astro/src/templates/starter/meta.json +++ b/packages/create-astro/src/templates/starter/meta.json @@ -1,5 +1,5 @@ { - "title": "Getting Started", - "description": "a friendly starting point for new astronauts", - "rank": 999 + "title": "Getting Started", + "description": "a friendly starting point for new astronauts", + "rank": 999 } diff --git a/packages/create-astro/src/utils.ts b/packages/create-astro/src/utils.ts index 790a64cf9..52e56f5f0 100644 --- a/packages/create-astro/src/utils.ts +++ b/packages/create-astro/src/utils.ts @@ -6,8 +6,8 @@ import decompress from 'decompress'; const listeners = new Map(); -export async function addProcessListeners(handlers: [NodeJS.Signals|string, NodeJS.SignalsListener][]) { - for (const [event,handler] of handlers) { +export async function addProcessListeners(handlers: [NodeJS.Signals | string, NodeJS.SignalsListener][]) { + for (const [event, handler] of handlers) { listeners.set(event, handler); process.once(event as NodeJS.Signals, handler); } @@ -21,18 +21,20 @@ export async function cancelProcessListeners() { } export async function getTemplates() { - const templatesRoot = fileURLToPath(new URL('./templates', import.meta.url)); - const templateFiles = await fs.readdir(templatesRoot, 'utf8'); - const templates = templateFiles.filter(t => t.endsWith('.tgz')); - const metafile = templateFiles.find(t => t.endsWith('meta.json')); + const templatesRoot = fileURLToPath(new URL('./templates', import.meta.url)); + const templateFiles = await fs.readdir(templatesRoot, 'utf8'); + const templates = templateFiles.filter((t) => t.endsWith('.tgz')); + const metafile = templateFiles.find((t) => t.endsWith('meta.json')); - const meta = await fs.readFile(resolve(templatesRoot, metafile)).then(r => JSON.parse(r.toString())); + const meta = await fs.readFile(resolve(templatesRoot, metafile)).then((r) => JSON.parse(r.toString())); - return templates.map(template => { + return templates + .map((template) => { const value = basename(template, '.tgz'); if (meta[value]) return { ...meta[value], value }; return { value }; - }).sort((a, b) => { + }) + .sort((a, b) => { const aRank = a.rank ?? 0; const bRank = b.rank ?? 0; if (aRank > bRank) return -1; @@ -49,28 +51,29 @@ export async function rewriteFiles(projectName: string) { const tasks = []; tasks.push(fs.rename(resolve(dest, '_gitignore'), resolve(dest, '.gitignore'))); tasks.push( - fs.readFile(resolve(dest, 'package.json')) - .then(res => JSON.parse(res.toString())) - .then(json => JSON.stringify({ ...json, name: getValidPackageName(projectName) }, null, 2)) - .then(res => fs.writeFile(resolve(dest, 'package.json'), res)) + fs + .readFile(resolve(dest, 'package.json')) + .then((res) => JSON.parse(res.toString())) + .then((json) => JSON.stringify({ ...json, name: getValidPackageName(projectName) }, null, 2)) + .then((res) => fs.writeFile(resolve(dest, 'package.json'), res)) ); return Promise.all(tasks); } -export async function prepareTemplate(use: 'npm'|'yarn', name: string, dest: string) { - const projectName = dest; - dest = resolve(dest); - const template = fileURLToPath(new URL(`./templates/${name}.tgz`, import.meta.url)); - await decompress(template, dest); - await rewriteFiles(projectName); - try { - await run(use, use === 'npm' ? 'i' : null, dest); - } catch (e) { - cleanup(true); - } - isDone = true; - return; +export async function prepareTemplate(use: 'npm' | 'yarn', name: string, dest: string) { + const projectName = dest; + dest = resolve(dest); + const template = fileURLToPath(new URL(`./templates/${name}.tgz`, import.meta.url)); + await decompress(template, dest); + await rewriteFiles(projectName); + try { + await run(use, use === 'npm' ? 'i' : null, dest); + } catch (e) { + cleanup(true); + } + isDone = true; + return; } export function cleanup(didError = false) { @@ -81,10 +84,10 @@ export function cleanup(didError = false) { } export function killChildren() { - childrenProcesses.forEach(p => p.kill('SIGINT')); + childrenProcesses.forEach((p) => p.kill('SIGINT')); } -export function run(pkgManager: 'npm'|'yarn', command: string, projectPath: string, stdio: any = 'ignore'): Promise { +export function run(pkgManager: 'npm' | 'yarn', command: string, projectPath: string, stdio: any = 'ignore'): Promise { return new Promise((resolve, reject) => { const p = spawn(pkgManager, command ? [command] : [], { shell: true, @@ -102,40 +105,40 @@ export function isWin() { } export function isEmpty(path) { - try { - const files = readdirSync(resolve(path)); - if (files.length > 0) { - return false; - } else { - return true; - } - } catch (err) { - if (err.code !== 'ENOENT') throw err; + try { + const files = readdirSync(resolve(path)); + if (files.length > 0) { + return false; + } else { + return true; } - return true; + } catch (err) { + if (err.code !== 'ENOENT') throw err; + } + return true; } export function emptyDir(dir) { dir = resolve(dir); if (!existsSync(dir)) { - return + return; } for (const file of readdirSync(dir)) { - const abs = resolve(dir, file) + const abs = resolve(dir, file); if (lstatSync(abs).isDirectory()) { - emptyDir(abs) - rmdirSync(abs) + emptyDir(abs); + rmdirSync(abs); } else { - unlinkSync(abs) + unlinkSync(abs); } } } export function getValidPackageName(projectName: string) { - const packageNameRegExp = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/ + const packageNameRegExp = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/; if (packageNameRegExp.test(projectName)) { - return projectName + return projectName; } return projectName diff --git a/scripts/package.json b/scripts/package.json index 4ece70b11..ed2142e0d 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -1,16 +1,16 @@ { - "name": "astro-scripts", - "version": "0.0.1", - "private": true, - "type": "module", - "main": "./index.js", - "bin": { - "astro-scripts": "./index.js" - }, - "dependencies": { - "arg": "^5.0.0", - "esbuild": "^0.11.16", - "globby": "^11.0.3", - "tar": "^6.1.0" - } + "name": "astro-scripts", + "version": "0.0.1", + "private": true, + "type": "module", + "main": "./index.js", + "bin": { + "astro-scripts": "./index.js" + }, + "dependencies": { + "arg": "^5.0.0", + "esbuild": "^0.11.16", + "globby": "^11.0.3", + "tar": "^6.1.0" + } } diff --git a/scripts/utils/svelte-plugin.js b/scripts/utils/svelte-plugin.js index 84839ffd7..cd65a5fdf 100644 --- a/scripts/utils/svelte-plugin.js +++ b/scripts/utils/svelte-plugin.js @@ -4,57 +4,58 @@ import { relative, isAbsolute, join, dirname } from 'path'; import { promises as fs } from 'fs'; const convertMessage = ({ message, start, end, filename, frame }) => ({ - text: message, - location: start && end && { - file: filename, - line: start.line, - column: start.column, - length: start.line === end.line ? end.column - start.column : 0, - lineText: frame, + text: message, + location: start && + end && { + file: filename, + line: start.line, + column: start.column, + length: start.line === end.line ? end.column - start.column : 0, + lineText: frame, }, -}) +}); const handleLoad = async (args, generate) => { - const { path } = args; - const source = await fs.readFile(path, 'utf8'); - const filename = relative(process.cwd(), path) + const { path } = args; + const source = await fs.readFile(path, 'utf8'); + const filename = relative(process.cwd(), path); - try { - let compileOptions = { css: false, generate, hydratable: true }; + try { + let compileOptions = { css: false, generate, hydratable: true }; - let { js, warnings } = compile(source, { ...compileOptions, filename }) - let contents = js.code + `\n//# sourceMappingURL=` + js.map.toUrl() + let { js, warnings } = compile(source, { ...compileOptions, filename }); + let contents = js.code + `\n//# sourceMappingURL=` + js.map.toUrl(); - return { loader: 'js', contents, resolveDir: dirname(path), warnings: warnings.map(w => convertMessage(w)) }; - } catch (e) { - return { errors: [convertMessage(e)] } - } -} + return { loader: 'js', contents, resolveDir: dirname(path), warnings: warnings.map((w) => convertMessage(w)) }; + } catch (e) { + return { errors: [convertMessage(e)] }; + } +}; export default function sveltePlugin() { - return { - name: 'svelte-esbuild', - setup(build) { - build.onResolve({ filter: /\.svelte$/ }, args => { - let path = args.path.replace(/\.(?:client|server)/, ''); - path = isAbsolute(path) ? path : join(args.resolveDir, path) - - if (/\.client\.svelte$/.test(args.path)) { - return { - path, - namespace: 'svelte:client', - } - } + return { + name: 'svelte-esbuild', + setup(build) { + build.onResolve({ filter: /\.svelte$/ }, (args) => { + let path = args.path.replace(/\.(?:client|server)/, ''); + path = isAbsolute(path) ? path : join(args.resolveDir, path); - if (/\.server\.svelte$/.test(args.path)) { - return { - path, - namespace: 'svelte:server', - } - } - }); - build.onLoad({ filter: /.*/, namespace: 'svelte:client' }, (args) => handleLoad(args, 'dom')) - build.onLoad({ filter: /.*/, namespace: 'svelte:server' }, (args) => handleLoad(args, 'ssr')) - }, - } + if (/\.client\.svelte$/.test(args.path)) { + return { + path, + namespace: 'svelte:client', + }; + } + + if (/\.server\.svelte$/.test(args.path)) { + return { + path, + namespace: 'svelte:server', + }; + } + }); + build.onLoad({ filter: /.*/, namespace: 'svelte:client' }, (args) => handleLoad(args, 'dom')); + build.onLoad({ filter: /.*/, namespace: 'svelte:server' }, (args) => handleLoad(args, 'ssr')); + }, + }; } diff --git a/tools/prettier-plugin-astro/index.js b/tools/prettier-plugin-astro/index.js index 54163459c..8bfdcd3e8 100644 --- a/tools/prettier-plugin-astro/index.js +++ b/tools/prettier-plugin-astro/index.js @@ -42,7 +42,7 @@ module.exports.parsers = { return node.end; }, astFormat: 'astro-expression', - } + }, }; const findExpressionsInAST = (node, collect = []) => { @@ -50,24 +50,24 @@ const findExpressionsInAST = (node, collect = []) => { return collect.concat(node); } if (node.children) { - collect.push(...[].concat(...node.children.map(child => findExpressionsInAST(child)))); + collect.push(...[].concat(...node.children.map((child) => findExpressionsInAST(child)))); } return collect; -} +}; -const formatExpression = ({ expression: { codeChunks, children }}, text, options) => { +const formatExpression = ({ expression: { codeChunks, children } }, text, options) => { if (children.length === 0) { const codeStart = codeChunks[0]; // If no children, there should only exist a single chunk. if (codeStart && [`'`, `"`].includes(codeStart[0])) { - return `` + return ``; } return `{${codeChunks.join('')}}`; } return ``; -} +}; -const isAstroScript = (node) => node.type === 'concat' && node.parts[0] === ' v === '$'); +const isAstroScript = (node) => node.type === 'concat' && node.parts[0] === ' v === '$'); const walkDoc = (doc) => { let inAstroScript = false; @@ -77,38 +77,38 @@ const walkDoc = (doc) => { inAstroScript = true; parent.contents = { type: 'concat', parts: ['{'] }; } - return node.parts.map(part => recurse(part, { parent: node })); + return node.parts.map((part) => recurse(part, { parent: node })); } if (inAstroScript) { if (node.type === 'break-parent') { - parent.parts = parent.parts.filter(part => !['break-parent', 'line'].includes(part.type)); + parent.parts = parent.parts.filter((part) => !['break-parent', 'line'].includes(part.type)); } if (node.type === 'indent') { - parent.parts = parent.parts.map(part => { + parent.parts = parent.parts.map((part) => { if (part.type !== 'indent') return part; return { type: 'concat', - parts: [part.contents] - } - }) + parts: [part.contents], + }; + }); } if (typeof node === 'string' && node.endsWith(';')) { - parent.parts = parent.parts.map(part => { + parent.parts = parent.parts.map((part) => { if (typeof part === 'string' && part.endsWith(';')) return part.slice(0, -1); return part; }); } if (node === '') { - parent.parts = parent.parts.map(part => part === '' ? '}' : part); + parent.parts = parent.parts.map((part) => (part === '' ? '}' : part)); inAstroScript = false; } } if (['group', 'indent'].includes(node.type)) { return recurse(node.contents, { parent: node }); } - } + }; recurse(doc, { parent: null }); -} +}; /** @type {Record} */ module.exports.printers = { @@ -129,18 +129,20 @@ module.exports.printers = { if (node.type === 'Fragment' && node.isRoot) { const expressions = findExpressionsInAST(node); if (expressions.length > 0) { - const parts = [].concat(...expressions.map((expr, i, all) => { - const prev = all[i - 1]; - const start = node.text.slice((prev?.end ?? node.start) - node.start, expr.start - node.start); - const exprText = formatExpression(expr, node.text.slice(expr.start - node.start + 1, expr.end - node.start - 1), options); + const parts = [].concat( + ...expressions.map((expr, i, all) => { + const prev = all[i - 1]; + const start = node.text.slice((prev?.end ?? node.start) - node.start, expr.start - node.start); + const exprText = formatExpression(expr, node.text.slice(expr.start - node.start + 1, expr.end - node.start - 1), options); - if (i === all.length - 1) { - const end = node.text.slice(expr.end - node.start); - return [start, exprText, end] - } + if (i === all.length - 1) { + const end = node.text.slice(expr.end - node.start); + return [start, exprText, end]; + } - return [start, exprText] - })); + return [start, exprText]; + }) + ); const html = parts.join('\n'); const doc = textToDoc(html, { parser: 'html' }); walkDoc(doc); diff --git a/tools/prettier-plugin-astro/test/astro-prettier.test.js b/tools/prettier-plugin-astro/test/astro-prettier.test.js index 99ece2a30..cf0f0c87c 100644 --- a/tools/prettier-plugin-astro/test/astro-prettier.test.js +++ b/tools/prettier-plugin-astro/test/astro-prettier.test.js @@ -2,10 +2,10 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import { format } from './test-utils.js'; import { promises as fs } from 'fs'; -import { fileURLToPath } from 'url' +import { fileURLToPath } from 'url'; const Prettier = suite('Prettier formatting'); -const readFile = (path) => fs.readFile(fileURLToPath(new URL(`./fixtures${path}`, import.meta.url))).then(res => res.toString()) +const readFile = (path) => fs.readFile(fileURLToPath(new URL(`./fixtures${path}`, import.meta.url))).then((res) => res.toString()); /** * Utility to get `[src, out]` files diff --git a/tools/prettier-plugin-astro/test/package.json b/tools/prettier-plugin-astro/test/package.json index 472002573..3dbc1ca59 100644 --- a/tools/prettier-plugin-astro/test/package.json +++ b/tools/prettier-plugin-astro/test/package.json @@ -1,3 +1,3 @@ { - "type": "module" + "type": "module" } diff --git a/tools/vscode/languages/astro-language-configuration.json b/tools/vscode/languages/astro-language-configuration.json index a6e9cae46..fef8c8c7b 100644 --- a/tools/vscode/languages/astro-language-configuration.json +++ b/tools/vscode/languages/astro-language-configuration.json @@ -1,36 +1,36 @@ { - "comments": { - "blockComment": [ "" ] - }, - "brackets": [ - ["---", "---"], - [""], - ["<", ">"], - ["{", "}"], - ["(", ")"] - ], - "autoClosingPairs": [ - { "open": "{", "close": "}"}, - { "open": "[", "close": "]"}, - { "open": "(", "close": ")" }, - { "open": "'", "close": "'" }, - { "open": "\"", "close": "\"" }, - { "open": "", "notIn": [ "comment", "string" ]}, - { "open": "/**", "close": " */", "notIn": ["string"] } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - { "open": "'", "close": "'" }, - { "open": "\"", "close": "\"" }, - { "open": "{", "close": "}"}, - { "open": "[", "close": "]"}, - { "open": "(", "close": ")" }, - { "open": "<", "close": ">" } - ], - "folding": { - "markers": { - "start": "^\\s*", - "end": "^\\s*" - } - } + "comments": { + "blockComment": [""] + }, + "brackets": [ + ["---", "---"], + [""], + ["<", ">"], + ["{", "}"], + ["(", ")"] + ], + "autoClosingPairs": [ + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "'", "close": "'" }, + { "open": "\"", "close": "\"" }, + { "open": "", "notIn": ["comment", "string"] }, + { "open": "/**", "close": " */", "notIn": ["string"] } + ], + "autoCloseBefore": ";:.,=}])>` \n\t", + "surroundingPairs": [ + { "open": "'", "close": "'" }, + { "open": "\"", "close": "\"" }, + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "<", "close": ">" } + ], + "folding": { + "markers": { + "start": "^\\s*", + "end": "^\\s*" + } + } } diff --git a/tools/vscode/package.json b/tools/vscode/package.json index 75e1eb507..f663d8bcb 100644 --- a/tools/vscode/package.json +++ b/tools/vscode/package.json @@ -1,96 +1,96 @@ { - "name": "@astro.build/vscode", - "displayName": "Astro", - "description": "Language support for Astro", - "icon": "assets/icon.png", - "galleryBanner": { - "color": "#FF5D01", - "theme": "dark" - }, - "version": "0.3.0", - "author": "Astro", - "publisher": "astro-build", - "license": "MIT", - "scripts": { - "vscode:prepublish": "npm run build", - "bootstrap": "cd packages/client && yarn && cd ../server && yarn", - "build": "node scripts/build.mjs", - "watch": "node scripts/watch.mjs" - }, - "engines": { - "vscode": "^1.52.0" - }, - "activationEvents": [ - "onLanguage:astro" - ], - "dependencies": { - "vscode-html-languageservice": "^3.0.3", - "vscode-emmet-helper": "2.1.2" - }, - "devDependencies": { - "esbuild": "0.10.0", - "@astro-vscode/client": "file:./packages/client", - "@astro-vscode/server": "file:./packages/server" - }, - "main": "./dist/index.js", - "files": [ - "dist/", - "languages/", - "syntaxes/" - ], - "repository": { - "type": "git", - "directory": "vscode", - "url": "https://github.com/snowpackjs/astro" - }, - "contributes": { - "configuration": { - "type": "object", - "title": "Astro configuration", - "properties": { - "astro.trace.server": { - "scope": "window", - "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "Traces the communication between VS Code and the language server." - } - } - }, - "languages": [ - { - "id": "astro", - "extensions": [ - ".astro" - ], - "aliases": [ - "Astro" - ], - "configuration": "./languages/astro-language-configuration.json" - } - ], - "grammars": [ - { - "language": "astro", - "scopeName": "text.html.astro", - "path": "./syntaxes/astro.tmLanguage.json", - "injectTo": [ - "text.html.markdown" - ], - "embeddedLanguages": { - "text.html.astro": "astro", - "text.html": "html", - "source.css": "css", - "source.scss": "scss", - "source.sass": "sass", - "source.tsx": "typescriptreact", - "meta.embedded.block.frontmatter": "typescriptreact" - } - } - ] - } + "name": "@astro.build/vscode", + "displayName": "Astro", + "description": "Language support for Astro", + "icon": "assets/icon.png", + "galleryBanner": { + "color": "#FF5D01", + "theme": "dark" + }, + "version": "0.3.0", + "author": "Astro", + "publisher": "astro-build", + "license": "MIT", + "scripts": { + "vscode:prepublish": "npm run build", + "bootstrap": "cd packages/client && yarn && cd ../server && yarn", + "build": "node scripts/build.mjs", + "watch": "node scripts/watch.mjs" + }, + "engines": { + "vscode": "^1.52.0" + }, + "activationEvents": [ + "onLanguage:astro" + ], + "dependencies": { + "vscode-html-languageservice": "^3.0.3", + "vscode-emmet-helper": "2.1.2" + }, + "devDependencies": { + "esbuild": "0.10.0", + "@astro-vscode/client": "file:./packages/client", + "@astro-vscode/server": "file:./packages/server" + }, + "main": "./dist/index.js", + "files": [ + "dist/", + "languages/", + "syntaxes/" + ], + "repository": { + "type": "git", + "directory": "vscode", + "url": "https://github.com/snowpackjs/astro" + }, + "contributes": { + "configuration": { + "type": "object", + "title": "Astro configuration", + "properties": { + "astro.trace.server": { + "scope": "window", + "type": "string", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "Traces the communication between VS Code and the language server." + } + } + }, + "languages": [ + { + "id": "astro", + "extensions": [ + ".astro" + ], + "aliases": [ + "Astro" + ], + "configuration": "./languages/astro-language-configuration.json" + } + ], + "grammars": [ + { + "language": "astro", + "scopeName": "text.html.astro", + "path": "./syntaxes/astro.tmLanguage.json", + "injectTo": [ + "text.html.markdown" + ], + "embeddedLanguages": { + "text.html.astro": "astro", + "text.html": "html", + "source.css": "css", + "source.scss": "scss", + "source.sass": "sass", + "source.tsx": "typescriptreact", + "meta.embedded.block.frontmatter": "typescriptreact" + } + } + ] + } } diff --git a/tools/vscode/packages/client/package.json b/tools/vscode/packages/client/package.json index 3a693790d..991f538dc 100644 --- a/tools/vscode/packages/client/package.json +++ b/tools/vscode/packages/client/package.json @@ -1,12 +1,12 @@ { - "name": "@astro-vscode/client", - "version": "0.1.0", - "author": "Skypack", - "license": "MIT", - "dependencies": { - "vscode-languageclient": "next" - }, - "devDependencies": { - "@types/vscode": "latest" - } + "name": "@astro-vscode/client", + "version": "0.1.0", + "author": "Skypack", + "license": "MIT", + "dependencies": { + "vscode-languageclient": "next" + }, + "devDependencies": { + "@types/vscode": "latest" + } } diff --git a/tools/vscode/packages/client/src/html/autoClose.ts b/tools/vscode/packages/client/src/html/autoClose.ts index 0dbce66c8..a3b90f615 100644 --- a/tools/vscode/packages/client/src/html/autoClose.ts +++ b/tools/vscode/packages/client/src/html/autoClose.ts @@ -12,97 +12,78 @@ import { TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol'; /** */ export function activateTagClosing( - tagProvider: (document: TextDocument, position: Position) => Thenable, - supportedLanguages: { [id: string]: boolean }, - configName: string + tagProvider: (document: TextDocument, position: Position) => Thenable, + supportedLanguages: { [id: string]: boolean }, + configName: string ): Disposable { - const disposables: Disposable[] = []; - workspace.onDidChangeTextDocument( - (event) => onDidChangeTextDocument(event.document, event.contentChanges), - null, - disposables - ); + const disposables: Disposable[] = []; + workspace.onDidChangeTextDocument((event) => onDidChangeTextDocument(event.document, event.contentChanges), null, disposables); - let isEnabled = false; - updateEnabledState(); - window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables); + let isEnabled = false; + updateEnabledState(); + window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables); - let timeout: NodeJS.Timer | undefined = void 0; + let timeout: NodeJS.Timer | undefined = void 0; - /** Check if this feature is enabled */ - function updateEnabledState() { - isEnabled = false; - const editor = window.activeTextEditor; - if (!editor) { - return; - } - const document = editor.document; - if (!supportedLanguages[document.languageId]) { - return; - } - if (!workspace.getConfiguration(void 0, document.uri).get(configName)) { - return; - } - isEnabled = true; + /** Check if this feature is enabled */ + function updateEnabledState() { + isEnabled = false; + const editor = window.activeTextEditor; + if (!editor) { + return; } - - /** Handle text document changes */ - function onDidChangeTextDocument( - document: TextDocument, - changes: readonly TextDocumentContentChangeEvent[] - ) { - if (!isEnabled) { - return; - } - const activeDocument = window.activeTextEditor && window.activeTextEditor.document; - if (document !== activeDocument || changes.length === 0) { - return; - } - if (typeof timeout !== 'undefined') { - clearTimeout(timeout); - } - const lastChange = changes[changes.length - 1]; - const lastCharacter = lastChange.text[lastChange.text.length - 1]; - if ( - ('range' in lastChange && (lastChange.rangeLength ?? 0) > 0) || - (lastCharacter !== '>' && lastCharacter !== '/') - ) { - return; - } - const rangeStart = - 'range' in lastChange - ? lastChange.range.start - : new Position(0, document.getText().length); - const version = document.version; - timeout = setTimeout(() => { - const position = new Position( - rangeStart.line, - rangeStart.character + lastChange.text.length - ); - tagProvider(document, position).then((text) => { - if (text && isEnabled) { - const activeEditor = window.activeTextEditor; - if (activeEditor) { - const activeDocument = activeEditor.document; - if (document === activeDocument && activeDocument.version === version) { - const selections = activeEditor.selections; - if ( - selections.length && - selections.some((s) => s.active.isEqual(position)) - ) { - activeEditor.insertSnippet( - new SnippetString(text), - selections.map((s) => s.active) - ); - } else { - activeEditor.insertSnippet(new SnippetString(text), position); - } - } - } - } - }); - timeout = void 0; - }, 100); + const document = editor.document; + if (!supportedLanguages[document.languageId]) { + return; } - return Disposable.from(...disposables); + if (!workspace.getConfiguration(void 0, document.uri).get(configName)) { + return; + } + isEnabled = true; + } + + /** Handle text document changes */ + function onDidChangeTextDocument(document: TextDocument, changes: readonly TextDocumentContentChangeEvent[]) { + if (!isEnabled) { + return; + } + const activeDocument = window.activeTextEditor && window.activeTextEditor.document; + if (document !== activeDocument || changes.length === 0) { + return; + } + if (typeof timeout !== 'undefined') { + clearTimeout(timeout); + } + const lastChange = changes[changes.length - 1]; + const lastCharacter = lastChange.text[lastChange.text.length - 1]; + if (('range' in lastChange && (lastChange.rangeLength ?? 0) > 0) || (lastCharacter !== '>' && lastCharacter !== '/')) { + return; + } + const rangeStart = 'range' in lastChange ? lastChange.range.start : new Position(0, document.getText().length); + const version = document.version; + timeout = setTimeout(() => { + const position = new Position(rangeStart.line, rangeStart.character + lastChange.text.length); + tagProvider(document, position).then((text) => { + if (text && isEnabled) { + const activeEditor = window.activeTextEditor; + if (activeEditor) { + const activeDocument = activeEditor.document; + if (document === activeDocument && activeDocument.version === version) { + const selections = activeEditor.selections; + if (selections.length && selections.some((s) => s.active.isEqual(position))) { + activeEditor.insertSnippet( + new SnippetString(text), + selections.map((s) => s.active) + ); + } else { + activeEditor.insertSnippet(new SnippetString(text), position); + } + } + } + } + }); + timeout = void 0; + }, 100); + } + return Disposable.from(...disposables); } diff --git a/tools/vscode/packages/client/tsconfig.json b/tools/vscode/packages/client/tsconfig.json index 51e95f536..417801227 100644 --- a/tools/vscode/packages/client/tsconfig.json +++ b/tools/vscode/packages/client/tsconfig.json @@ -1,12 +1,10 @@ { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - }, - "include": ["src"], - "exclude": ["node_modules"], - "references": [ - { "path": "../server" } - ] + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules"], + "references": [{ "path": "../server" }] } diff --git a/tools/vscode/packages/server/src/core/config/ConfigManager.ts b/tools/vscode/packages/server/src/core/config/ConfigManager.ts index 4c1c23b13..1e795ab96 100644 --- a/tools/vscode/packages/server/src/core/config/ConfigManager.ts +++ b/tools/vscode/packages/server/src/core/config/ConfigManager.ts @@ -1,13 +1,13 @@ import { VSCodeEmmetConfig } from 'vscode-emmet-helper'; export class ConfigManager { - private emmetConfig: VSCodeEmmetConfig = {}; - - updateEmmetConfig(config: VSCodeEmmetConfig): void { - this.emmetConfig = config || {}; - } + private emmetConfig: VSCodeEmmetConfig = {}; - getEmmetConfig(): VSCodeEmmetConfig { - return this.emmetConfig; - } + updateEmmetConfig(config: VSCodeEmmetConfig): void { + this.emmetConfig = config || {}; + } + + getEmmetConfig(): VSCodeEmmetConfig { + return this.emmetConfig; + } } diff --git a/tools/vscode/packages/server/src/core/documents/Document.ts b/tools/vscode/packages/server/src/core/documents/Document.ts index 4f90813ee..93217e891 100644 --- a/tools/vscode/packages/server/src/core/documents/Document.ts +++ b/tools/vscode/packages/server/src/core/documents/Document.ts @@ -7,153 +7,147 @@ import { parseHtml } from './parseHtml'; import { parseAstro, AstroDocument } from './parseAstro'; export class Document implements TextDocument { - - private content: string; - - languageId = 'astro'; - version = 0; - html!: HTMLDocument; - astro!: AstroDocument; + private content: string; - constructor(public uri: string, text: string) { - this.content = text; - this.updateDocInfo(); + languageId = 'astro'; + version = 0; + html!: HTMLDocument; + astro!: AstroDocument; + + constructor(public uri: string, text: string) { + this.content = text; + this.updateDocInfo(); + } + + private updateDocInfo() { + this.html = parseHtml(this.content); + this.astro = parseAstro(this.content); + } + + setText(text: string) { + this.content = text; + this.version++; + this.updateDocInfo(); + } + + /** + * Update the text between two positions. + * @param text The new text slice + * @param start Start offset of the new text + * @param end End offset of the new text + */ + update(text: string, start: number, end: number): void { + const content = this.getText(); + this.setText(content.slice(0, start) + text + content.slice(end)); + } + + getText(): string { + return this.content; + } + + /** + * Get the line and character based on the offset + * @param offset The index of the position + */ + positionAt(offset: number): Position { + offset = clamp(offset, 0, this.getTextLength()); + + const lineOffsets = this.getLineOffsets(); + let low = 0; + let high = lineOffsets.length; + if (high === 0) { + return Position.create(0, offset); } - private updateDocInfo() { - this.html = parseHtml(this.content); - this.astro = parseAstro(this.content); + while (low < high) { + const mid = Math.floor((low + high) / 2); + if (lineOffsets[mid] > offset) { + high = mid; + } else { + low = mid + 1; + } } - setText(text: string) { - this.content = text; - this.version++; - this.updateDocInfo(); + // low is the least x for which the line offset is larger than the current offset + // or array.length if no line offset is larger than the current offset + const line = low - 1; + return Position.create(line, offset - lineOffsets[line]); + } + + /** + * Get the index of the line and character position + * @param position Line and character position + */ + offsetAt(position: Position): number { + const lineOffsets = this.getLineOffsets(); + + if (position.line >= lineOffsets.length) { + return this.getTextLength(); + } else if (position.line < 0) { + return 0; } - /** - * Update the text between two positions. - * @param text The new text slice - * @param start Start offset of the new text - * @param end End offset of the new text - */ - update(text: string, start: number, end: number): void { - const content = this.getText(); - this.setText(content.slice(0, start) + text + content.slice(end)); + const lineOffset = lineOffsets[position.line]; + const nextLineOffset = position.line + 1 < lineOffsets.length ? lineOffsets[position.line + 1] : this.getTextLength(); + + return clamp(nextLineOffset, lineOffset, lineOffset + position.character); + } + + getLineUntilOffset(offset: number): string { + const { line, character } = this.positionAt(offset); + return this.lines[line].slice(0, character); + } + + private getLineOffsets() { + const lineOffsets = []; + const text = this.getText(); + let isLineStart = true; + + for (let i = 0; i < text.length; i++) { + if (isLineStart) { + lineOffsets.push(i); + isLineStart = false; + } + const ch = text.charAt(i); + isLineStart = ch === '\r' || ch === '\n'; + if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { + i++; + } } - getText(): string { - return this.content + if (isLineStart && text.length > 0) { + lineOffsets.push(text.length); } - /** - * Get the line and character based on the offset - * @param offset The index of the position - */ - positionAt(offset: number): Position { - offset = clamp(offset, 0, this.getTextLength()); + return lineOffsets; + } - const lineOffsets = this.getLineOffsets(); - let low = 0; - let high = lineOffsets.length; - if (high === 0) { - return Position.create(0, offset); - } + /** + * Get the length of the document's content + */ + getTextLength(): number { + return this.getText().length; + } - while (low < high) { - const mid = Math.floor((low + high) / 2); - if (lineOffsets[mid] > offset) { - high = mid; - } else { - low = mid + 1; - } - } + /** + * Returns the file path if the url scheme is file + */ + getFilePath(): string | null { + return urlToPath(this.uri); + } - // low is the least x for which the line offset is larger than the current offset - // or array.length if no line offset is larger than the current offset - const line = low - 1; - return Position.create(line, offset - lineOffsets[line]); - } + /** + * Get URL file path. + */ + getURL() { + return this.uri; + } - /** - * Get the index of the line and character position - * @param position Line and character position - */ - offsetAt(position: Position): number { - const lineOffsets = this.getLineOffsets(); - - if (position.line >= lineOffsets.length) { - return this.getTextLength(); - } else if (position.line < 0) { - return 0; - } - - const lineOffset = lineOffsets[position.line]; - const nextLineOffset = - position.line + 1 < lineOffsets.length - ? lineOffsets[position.line + 1] - : this.getTextLength(); - - return clamp(nextLineOffset, lineOffset, lineOffset + position.character); - } - - getLineUntilOffset(offset: number): string { - const { line, character } = this.positionAt(offset); - return this.lines[line].slice(0, character); - } - - private getLineOffsets() { - const lineOffsets = []; - const text = this.getText(); - let isLineStart = true; - - for (let i = 0; i < text.length; i++) { - if (isLineStart) { - lineOffsets.push(i); - isLineStart = false; - } - const ch = text.charAt(i); - isLineStart = ch === '\r' || ch === '\n'; - if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { - i++; - } - } - - if (isLineStart && text.length > 0) { - lineOffsets.push(text.length); - } - - return lineOffsets; - } - - /** - * Get the length of the document's content - */ - getTextLength(): number { - return this.getText().length; - } - - /** - * Returns the file path if the url scheme is file - */ - getFilePath(): string | null { - return urlToPath(this.uri); - } - - /** - * Get URL file path. - */ - getURL() { - return this.uri; - } - - - get lines(): string[] { - return this.getText().split(/\r?\n/); - } - - get lineCount(): number { - return this.lines.length; - } + get lines(): string[] { + return this.getText().split(/\r?\n/); + } + get lineCount(): number { + return this.lines.length; + } } diff --git a/tools/vscode/packages/server/src/core/documents/DocumentManager.ts b/tools/vscode/packages/server/src/core/documents/DocumentManager.ts index 6195514d8..7c9c168c1 100644 --- a/tools/vscode/packages/server/src/core/documents/DocumentManager.ts +++ b/tools/vscode/packages/server/src/core/documents/DocumentManager.ts @@ -1,104 +1,94 @@ import { EventEmitter } from 'events'; -import { - TextDocumentContentChangeEvent, - TextDocumentItem -} from 'vscode-languageserver'; +import { TextDocumentContentChangeEvent, TextDocumentItem } from 'vscode-languageserver'; import { Document } from './Document'; import { normalizeUri } from '../../utils'; export type DocumentEvent = 'documentOpen' | 'documentChange' | 'documentClose'; export class DocumentManager { - private emitter = new EventEmitter(); - private openedInClient = new Set(); - private documents: Map = new Map(); - private locked = new Set(); - private deleteCandidates = new Set(); + private emitter = new EventEmitter(); + private openedInClient = new Set(); + private documents: Map = new Map(); + private locked = new Set(); + private deleteCandidates = new Set(); - constructor( - private createDocument: (textDocument: { uri: string, text: string }) => Document - ) {} + constructor(private createDocument: (textDocument: { uri: string; text: string }) => Document) {} - get(uri: string) { - return this.documents.get(normalizeUri(uri)); + get(uri: string) { + return this.documents.get(normalizeUri(uri)); + } + + openDocument(textDocument: TextDocumentItem) { + let document: Document; + if (this.documents.has(textDocument.uri)) { + document = this.get(textDocument.uri) as Document; + document.setText(textDocument.text); + } else { + document = this.createDocument(textDocument); + this.documents.set(normalizeUri(textDocument.uri), document); + this.notify('documentOpen', document); } - openDocument(textDocument: TextDocumentItem) { - let document: Document; - if (this.documents.has(textDocument.uri)) { - document = this.get(textDocument.uri) as Document; - document.setText(textDocument.text); - } else { - document = this.createDocument(textDocument); - this.documents.set(normalizeUri(textDocument.uri), document); - this.notify('documentOpen', document); - } + this.notify('documentChange', document); - this.notify('documentChange', document); + return document; + } - return document; + closeDocument(uri: string) { + uri = normalizeUri(uri); + + const document = this.documents.get(uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); } - closeDocument(uri: string) { - uri = normalizeUri(uri); + this.notify('documentClose', document); - const document = this.documents.get(uri); - if (!document) { - throw new Error('Cannot call methods on an unopened document'); - } - - this.notify('documentClose', document); - - // Some plugin may prevent a document from actually being closed. - if (!this.locked.has(uri)) { - this.documents.delete(uri); - } else { - this.deleteCandidates.add(uri); - } - - this.openedInClient.delete(uri); + // Some plugin may prevent a document from actually being closed. + if (!this.locked.has(uri)) { + this.documents.delete(uri); + } else { + this.deleteCandidates.add(uri); } - updateDocument( - uri: string, - changes: TextDocumentContentChangeEvent[] - ) { - const document = this.documents.get(normalizeUri(uri)); - if (!document) { - throw new Error('Cannot call methods on an unopened document'); - } + this.openedInClient.delete(uri); + } - for (const change of changes) { - let start = 0; - let end = 0; - if ('range' in change) { - start = document.offsetAt(change.range.start); - end = document.offsetAt(change.range.end); - } else { - end = document.getTextLength(); - } - - document.update(change.text, start, end); - } - - this.notify('documentChange', document); + updateDocument(uri: string, changes: TextDocumentContentChangeEvent[]) { + const document = this.documents.get(normalizeUri(uri)); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); } - markAsOpenedInClient(uri: string) { - this.openedInClient.add(normalizeUri(uri)); + for (const change of changes) { + let start = 0; + let end = 0; + if ('range' in change) { + start = document.offsetAt(change.range.start); + end = document.offsetAt(change.range.end); + } else { + end = document.getTextLength(); + } + + document.update(change.text, start, end); } - getAllOpenedByClient() { - return Array.from(this.documents.entries()).filter((doc) => - this.openedInClient.has(doc[0]) - ); - } + this.notify('documentChange', document); + } - on(name: DocumentEvent, listener: (document: Document) => void) { - this.emitter.on(name, listener); - } + markAsOpenedInClient(uri: string) { + this.openedInClient.add(normalizeUri(uri)); + } - private notify(name: DocumentEvent, document: Document) { - this.emitter.emit(name, document); - } + getAllOpenedByClient() { + return Array.from(this.documents.entries()).filter((doc) => this.openedInClient.has(doc[0])); + } + + on(name: DocumentEvent, listener: (document: Document) => void) { + this.emitter.on(name, listener); + } + + private notify(name: DocumentEvent, document: Document) { + this.emitter.emit(name, document); + } } diff --git a/tools/vscode/packages/server/src/core/documents/parseAstro.ts b/tools/vscode/packages/server/src/core/documents/parseAstro.ts index e4f71721a..71c7764d8 100644 --- a/tools/vscode/packages/server/src/core/documents/parseAstro.ts +++ b/tools/vscode/packages/server/src/core/documents/parseAstro.ts @@ -1,74 +1,77 @@ import { getFirstNonWhitespaceIndex } from './utils'; interface Frontmatter { - state: null | 'open' | 'closed'; - startOffset: null | number; - endOffset: null | number; + state: null | 'open' | 'closed'; + startOffset: null | number; + endOffset: null | number; } interface Content { - firstNonWhitespaceOffset: null | number; + firstNonWhitespaceOffset: null | number; } export interface AstroDocument { - frontmatter: Frontmatter - content: Content; + frontmatter: Frontmatter; + content: Content; } /** Parses a document to collect metadata about Astro features */ export function parseAstro(content: string): AstroDocument { - const frontmatter = getFrontmatter(content) - return { - frontmatter, - content: getContent(content, frontmatter) - } + const frontmatter = getFrontmatter(content); + return { + frontmatter, + content: getContent(content, frontmatter), + }; } /** Get frontmatter metadata */ function getFrontmatter(content: string): Frontmatter { - /** Quickly check how many `---` blocks are in the document */ - function getFrontmatterState(): Frontmatter['state'] { - const parts = content.trim().split('---').length; - switch (parts) { - case 1: return null; - case 2: return 'open'; - default: return 'closed'; - } + /** Quickly check how many `---` blocks are in the document */ + function getFrontmatterState(): Frontmatter['state'] { + const parts = content.trim().split('---').length; + switch (parts) { + case 1: + return null; + case 2: + return 'open'; + default: + return 'closed'; } - const state = getFrontmatterState(); + } + const state = getFrontmatterState(); - /** Construct a range containing the document's frontmatter */ - function getFrontmatterOffsets(): [number|null, number|null] { - const startOffset = content.indexOf('---'); - if (startOffset === -1) return [null, null]; - const endOffset = content.slice(startOffset + 3).indexOf('---') + 3; - if (endOffset === -1) return [startOffset, null]; - return [startOffset, endOffset]; - } - const [startOffset, endOffset] = getFrontmatterOffsets(); + /** Construct a range containing the document's frontmatter */ + function getFrontmatterOffsets(): [number | null, number | null] { + const startOffset = content.indexOf('---'); + if (startOffset === -1) return [null, null]; + const endOffset = content.slice(startOffset + 3).indexOf('---') + 3; + if (endOffset === -1) return [startOffset, null]; + return [startOffset, endOffset]; + } + const [startOffset, endOffset] = getFrontmatterOffsets(); - return { - state, - startOffset, - endOffset - }; + return { + state, + startOffset, + endOffset, + }; } /** Get content metadata */ function getContent(content: string, frontmatter: Frontmatter): Content { - switch (frontmatter.state) { - case null: { - const offset = getFirstNonWhitespaceIndex(content); - return { firstNonWhitespaceOffset: offset === -1 ? null : offset } - } - case 'open': { - return { firstNonWhitespaceOffset: null } - } - case 'closed': { - const { endOffset } = frontmatter; - const end = (endOffset ?? 0) + 3; - const offset = getFirstNonWhitespaceIndex(content.slice(end)) - return { firstNonWhitespaceOffset: end + offset } - } + switch (frontmatter.state) { + case null: { + const offset = getFirstNonWhitespaceIndex(content); + return { firstNonWhitespaceOffset: offset === -1 ? null : offset }; } + case 'open': { + return { firstNonWhitespaceOffset: null }; + } + case 'closed': { + const { endOffset } = frontmatter; + const end = (endOffset ?? 0) + 3; + const offset = getFirstNonWhitespaceIndex(content.slice(end)); + return { firstNonWhitespaceOffset: end + offset }; + } + } } diff --git a/tools/vscode/packages/server/src/core/documents/parseHtml.ts b/tools/vscode/packages/server/src/core/documents/parseHtml.ts index 86af06008..f5de5f292 100644 --- a/tools/vscode/packages/server/src/core/documents/parseHtml.ts +++ b/tools/vscode/packages/server/src/core/documents/parseHtml.ts @@ -1,12 +1,4 @@ -import { - getLanguageService, - HTMLDocument, - TokenType, - ScannerState, - Scanner, - Node, - Position -} from 'vscode-html-languageservice'; +import { getLanguageService, HTMLDocument, TokenType, ScannerState, Scanner, Node, Position } from 'vscode-html-languageservice'; import { Document } from './Document'; import { isInsideExpression } from './utils'; @@ -16,154 +8,134 @@ const parser = getLanguageService(); * Parses text as HTML */ export function parseHtml(text: string): HTMLDocument { - const preprocessed = preprocess(text); + const preprocessed = preprocess(text); - // We can safely only set getText because only this is used for parsing - const parsedDoc = parser.parseHTMLDocument({ getText: () => preprocessed }); + // We can safely only set getText because only this is used for parsing + const parsedDoc = parser.parseHTMLDocument({ getText: () => preprocessed }); - return parsedDoc; + return parsedDoc; } -const createScanner = parser.createScanner as ( - input: string, - initialOffset?: number, - initialState?: ScannerState -) => Scanner; +const createScanner = parser.createScanner as (input: string, initialOffset?: number, initialState?: ScannerState) => Scanner; /** * scan the text and remove any `>` or `<` that cause the tag to end short, */ function preprocess(text: string) { - let scanner = createScanner(text); - let token = scanner.scan(); - let currentStartTagStart: number | null = null; + let scanner = createScanner(text); + let token = scanner.scan(); + let currentStartTagStart: number | null = null; - while (token !== TokenType.EOS) { - const offset = scanner.getTokenOffset(); + while (token !== TokenType.EOS) { + const offset = scanner.getTokenOffset(); - if (token === TokenType.StartTagOpen) { - currentStartTagStart = offset; - } - - if (token === TokenType.StartTagClose) { - if (shouldBlankStartOrEndTagLike(offset)) { - blankStartOrEndTagLike(offset); - } else { - currentStartTagStart = null; - } - } - - if (token === TokenType.StartTagSelfClose) { - currentStartTagStart = null; - } - - // - // https://github.com/microsoft/vscode-html-languageservice/blob/71806ef57be07e1068ee40900ef8b0899c80e68a/src/parser/htmlScanner.ts#L327 - if ( - token === TokenType.Unknown && - scanner.getScannerState() === ScannerState.WithinTag && - scanner.getTokenText() === '<' && - shouldBlankStartOrEndTagLike(offset) - ) { - blankStartOrEndTagLike(offset); - } - - token = scanner.scan(); + if (token === TokenType.StartTagOpen) { + currentStartTagStart = offset; } - return text; - - function shouldBlankStartOrEndTagLike(offset: number) { - // not null rather than falsy, otherwise it won't work on first tag(0) - return ( - currentStartTagStart !== null && - isInsideExpression(text, currentStartTagStart, offset) - ); + if (token === TokenType.StartTagClose) { + if (shouldBlankStartOrEndTagLike(offset)) { + blankStartOrEndTagLike(offset); + } else { + currentStartTagStart = null; + } } - function blankStartOrEndTagLike(offset: number) { - text = text.substring(0, offset) + ' ' + text.substring(offset + 1); - scanner = createScanner(text, offset, ScannerState.WithinTag); + if (token === TokenType.StartTagSelfClose) { + currentStartTagStart = null; } + + // + // https://github.com/microsoft/vscode-html-languageservice/blob/71806ef57be07e1068ee40900ef8b0899c80e68a/src/parser/htmlScanner.ts#L327 + if (token === TokenType.Unknown && scanner.getScannerState() === ScannerState.WithinTag && scanner.getTokenText() === '<' && shouldBlankStartOrEndTagLike(offset)) { + blankStartOrEndTagLike(offset); + } + + token = scanner.scan(); + } + + return text; + + function shouldBlankStartOrEndTagLike(offset: number) { + // not null rather than falsy, otherwise it won't work on first tag(0) + return currentStartTagStart !== null && isInsideExpression(text, currentStartTagStart, offset); + } + + function blankStartOrEndTagLike(offset: number) { + text = text.substring(0, offset) + ' ' + text.substring(offset + 1); + scanner = createScanner(text, offset, ScannerState.WithinTag); + } } export interface AttributeContext { - name: string; - inValue: boolean; - valueRange?: [number, number]; + name: string; + inValue: boolean; + valueRange?: [number, number]; } -export function getAttributeContextAtPosition( - document: Document, - position: Position -): AttributeContext | null { - const offset = document.offsetAt(position); - const { html } = document; - const tag = html.findNodeAt(offset); - - if (!inStartTag(offset, tag) || !tag.attributes) { - return null; - } - - const text = document.getText(); - const beforeStartTagEnd = - text.substring(0, tag.start) + preprocess(text.substring(tag.start, tag.startTagEnd)); - - const scanner = createScanner(beforeStartTagEnd, tag.start); - - let token = scanner.scan(); - let currentAttributeName: string | undefined; - const inTokenRange = () => - scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd(); - while (token != TokenType.EOS) { - // adopted from https://github.com/microsoft/vscode-html-languageservice/blob/2f7ae4df298ac2c299a40e9024d118f4a9dc0c68/src/services/htmlCompletion.ts#L402 - if (token === TokenType.AttributeName) { - currentAttributeName = scanner.getTokenText(); - - if (inTokenRange()) { - return { - name: currentAttributeName, - inValue: false - }; - } - } else if (token === TokenType.DelimiterAssign) { - if (scanner.getTokenEnd() === offset && currentAttributeName) { - const nextToken = scanner.scan(); - - return { - name: currentAttributeName, - inValue: true, - valueRange: [ - offset, - nextToken === TokenType.AttributeValue ? scanner.getTokenEnd() : offset - ] - }; - } - } else if (token === TokenType.AttributeValue) { - if (inTokenRange() && currentAttributeName) { - let start = scanner.getTokenOffset(); - let end = scanner.getTokenEnd(); - const char = text[start]; - - if (char === '"' || char === "'") { - start++; - end--; - } - - return { - name: currentAttributeName, - inValue: true, - valueRange: [start, end] - }; - } - currentAttributeName = undefined; - } - token = scanner.scan(); - } +export function getAttributeContextAtPosition(document: Document, position: Position): AttributeContext | null { + const offset = document.offsetAt(position); + const { html } = document; + const tag = html.findNodeAt(offset); + if (!inStartTag(offset, tag) || !tag.attributes) { return null; + } + + const text = document.getText(); + const beforeStartTagEnd = text.substring(0, tag.start) + preprocess(text.substring(tag.start, tag.startTagEnd)); + + const scanner = createScanner(beforeStartTagEnd, tag.start); + + let token = scanner.scan(); + let currentAttributeName: string | undefined; + const inTokenRange = () => scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd(); + while (token != TokenType.EOS) { + // adopted from https://github.com/microsoft/vscode-html-languageservice/blob/2f7ae4df298ac2c299a40e9024d118f4a9dc0c68/src/services/htmlCompletion.ts#L402 + if (token === TokenType.AttributeName) { + currentAttributeName = scanner.getTokenText(); + + if (inTokenRange()) { + return { + name: currentAttributeName, + inValue: false, + }; + } + } else if (token === TokenType.DelimiterAssign) { + if (scanner.getTokenEnd() === offset && currentAttributeName) { + const nextToken = scanner.scan(); + + return { + name: currentAttributeName, + inValue: true, + valueRange: [offset, nextToken === TokenType.AttributeValue ? scanner.getTokenEnd() : offset], + }; + } + } else if (token === TokenType.AttributeValue) { + if (inTokenRange() && currentAttributeName) { + let start = scanner.getTokenOffset(); + let end = scanner.getTokenEnd(); + const char = text[start]; + + if (char === '"' || char === "'") { + start++; + end--; + } + + return { + name: currentAttributeName, + inValue: true, + valueRange: [start, end], + }; + } + currentAttributeName = undefined; + } + token = scanner.scan(); + } + + return null; } function inStartTag(offset: number, node: Node) { - return offset > node.start && node.startTagEnd != undefined && offset < node.startTagEnd; + return offset > node.start && node.startTagEnd != undefined && offset < node.startTagEnd; } diff --git a/tools/vscode/packages/server/src/core/documents/utils.ts b/tools/vscode/packages/server/src/core/documents/utils.ts index 6c69014d5..3d12f35a3 100644 --- a/tools/vscode/packages/server/src/core/documents/utils.ts +++ b/tools/vscode/packages/server/src/core/documents/utils.ts @@ -5,63 +5,52 @@ import { clamp } from '../../utils'; * Gets word range at position. * Delimiter is by default a whitespace, but can be adjusted. */ -export function getWordRangeAt( - str: string, - pos: number, - delimiterRegex = { left: /\S+$/, right: /\s/ } -): { start: number; end: number } { - let start = str.slice(0, pos).search(delimiterRegex.left); - if (start < 0) { - start = pos; - } +export function getWordRangeAt(str: string, pos: number, delimiterRegex = { left: /\S+$/, right: /\s/ }): { start: number; end: number } { + let start = str.slice(0, pos).search(delimiterRegex.left); + if (start < 0) { + start = pos; + } - let end = str.slice(pos).search(delimiterRegex.right); - if (end < 0) { - end = str.length; - } else { - end = end + pos; - } + let end = str.slice(pos).search(delimiterRegex.right); + if (end < 0) { + end = str.length; + } else { + end = end + pos; + } - return { start, end }; + return { start, end }; } /** * Gets word at position. * Delimiter is by default a whitespace, but can be adjusted. */ -export function getWordAt( - str: string, - pos: number, - delimiterRegex = { left: /\S+$/, right: /\s/ } -): string { - const { start, end } = getWordRangeAt(str, pos, delimiterRegex); - return str.slice(start, end); +export function getWordAt(str: string, pos: number, delimiterRegex = { left: /\S+$/, right: /\s/ }): string { + const { start, end } = getWordRangeAt(str, pos, delimiterRegex); + return str.slice(start, end); } /** * Gets index of first-non-whitespace character. */ export function getFirstNonWhitespaceIndex(str: string): number { - return str.length - str.trimStart().length; + return str.length - str.trimStart().length; } /** checks if a position is currently inside of an expression */ export function isInsideExpression(html: string, tagStart: number, position: number) { - const charactersInNode = html.substring(tagStart, position); - return charactersInNode.lastIndexOf('{') > charactersInNode.lastIndexOf('}'); + const charactersInNode = html.substring(tagStart, position); + return charactersInNode.lastIndexOf('{') > charactersInNode.lastIndexOf('}'); } /** - * Returns if a given offset is inside of the document frontmatter + * Returns if a given offset is inside of the document frontmatter */ -export function isInsideFrontmatter( - text: string, - offset: number -): boolean { - let start = text.slice(0, offset).trim().split('---').length; - let end = text.slice(offset).trim().split('---').length; +export function isInsideFrontmatter(text: string, offset: number): boolean { + let start = text.slice(0, offset).trim().split('---').length; + let end = text.slice(offset).trim().split('---').length; - return start > 1 && start < 3 && end >= 1; + return start > 1 && start < 3 && end >= 1; } /** @@ -70,28 +59,28 @@ export function isInsideFrontmatter( * @param text The text for which the position should be retrived */ export function positionAt(offset: number, text: string): Position { - offset = clamp(offset, 0, text.length); + offset = clamp(offset, 0, text.length); - const lineOffsets = getLineOffsets(text); - let low = 0; - let high = lineOffsets.length; - if (high === 0) { - return Position.create(0, offset); + const lineOffsets = getLineOffsets(text); + let low = 0; + let high = lineOffsets.length; + if (high === 0) { + return Position.create(0, offset); + } + + while (low < high) { + const mid = Math.floor((low + high) / 2); + if (lineOffsets[mid] > offset) { + high = mid; + } else { + low = mid + 1; } + } - while (low < high) { - const mid = Math.floor((low + high) / 2); - if (lineOffsets[mid] > offset) { - high = mid; - } else { - low = mid + 1; - } - } - - // low is the least x for which the line offset is larger than the current offset - // or array.length if no line offset is larger than the current offset - const line = low - 1; - return Position.create(line, offset - lineOffsets[line]); + // low is the least x for which the line offset is larger than the current offset + // or array.length if no line offset is larger than the current offset + const line = low - 1; + return Position.create(line, offset - lineOffsets[line]); } /** @@ -100,40 +89,39 @@ export function positionAt(offset: number, text: string): Position { * @param text The text for which the offset should be retrived */ export function offsetAt(position: Position, text: string): number { - const lineOffsets = getLineOffsets(text); + const lineOffsets = getLineOffsets(text); - if (position.line >= lineOffsets.length) { - return text.length; - } else if (position.line < 0) { - return 0; - } + if (position.line >= lineOffsets.length) { + return text.length; + } else if (position.line < 0) { + return 0; + } - const lineOffset = lineOffsets[position.line]; - const nextLineOffset = - position.line + 1 < lineOffsets.length ? lineOffsets[position.line + 1] : text.length; + const lineOffset = lineOffsets[position.line]; + const nextLineOffset = position.line + 1 < lineOffsets.length ? lineOffsets[position.line + 1] : text.length; - return clamp(nextLineOffset, lineOffset, lineOffset + position.character); + return clamp(nextLineOffset, lineOffset, lineOffset + position.character); } function getLineOffsets(text: string) { - const lineOffsets = []; - let isLineStart = true; + const lineOffsets = []; + let isLineStart = true; - for (let i = 0; i < text.length; i++) { - if (isLineStart) { - lineOffsets.push(i); - isLineStart = false; - } - const ch = text.charAt(i); - isLineStart = ch === '\r' || ch === '\n'; - if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { - i++; - } + for (let i = 0; i < text.length; i++) { + if (isLineStart) { + lineOffsets.push(i); + isLineStart = false; } - - if (isLineStart && text.length > 0) { - lineOffsets.push(text.length); + const ch = text.charAt(i); + isLineStart = ch === '\r' || ch === '\n'; + if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { + i++; } + } - return lineOffsets; + if (isLineStart && text.length > 0) { + lineOffsets.push(text.length); + } + + return lineOffsets; } diff --git a/tools/vscode/packages/server/src/index.ts b/tools/vscode/packages/server/src/index.ts index f72ad550b..528d3cb9d 100644 --- a/tools/vscode/packages/server/src/index.ts +++ b/tools/vscode/packages/server/src/index.ts @@ -71,10 +71,12 @@ export function startServer() { connection.onDidChangeTextDocument((evt) => docManager.updateDocument(evt.textDocument.uri, evt.contentChanges)); connection.onDidChangeWatchedFiles((evt) => { - const params = evt.changes.map(change => ({ - fileName: urlToPath(change.uri), - changeType: change.type - })).filter(change => !!change.fileName) + const params = evt.changes + .map((change) => ({ + fileName: urlToPath(change.uri), + changeType: change.type, + })) + .filter((change) => !!change.fileName); pluginHost.onWatchFileChanges(params); }); diff --git a/tools/vscode/packages/server/src/plugins/PluginHost.ts b/tools/vscode/packages/server/src/plugins/PluginHost.ts index 72f098ca1..037dd6e07 100644 --- a/tools/vscode/packages/server/src/plugins/PluginHost.ts +++ b/tools/vscode/packages/server/src/plugins/PluginHost.ts @@ -1,11 +1,4 @@ - -import { - CompletionContext, - CompletionItem, - CompletionList, - Position, - TextDocumentIdentifier, -} from 'vscode-languageserver'; +import { CompletionContext, CompletionItem, CompletionList, Position, TextDocumentIdentifier } from 'vscode-languageserver'; import type { DocumentManager } from '../core/documents'; import type * as d from './interfaces'; import { flatten } from '../utils'; @@ -13,154 +6,107 @@ import { FoldingRange } from 'vscode-languageserver-types'; // eslint-disable-next-line no-shadow enum ExecuteMode { - None, - FirstNonNull, - Collect + None, + FirstNonNull, + Collect, } export class PluginHost { - private plugins: d.Plugin[] = []; + private plugins: d.Plugin[] = []; - constructor(private documentsManager: DocumentManager) {} + constructor(private documentsManager: DocumentManager) {} - register(plugin: d.Plugin) { - this.plugins.push(plugin); + register(plugin: d.Plugin) { + this.plugins.push(plugin); + } + + async getCompletions(textDocument: TextDocumentIdentifier, position: Position, completionContext?: CompletionContext): Promise { + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); } - async getCompletions( - textDocument: TextDocumentIdentifier, - position: Position, - completionContext?: CompletionContext - ): Promise { - const document = this.getDocument(textDocument.uri); - if (!document) { - throw new Error('Cannot call methods on an unopened document'); + const completions = (await this.execute('getCompletions', [document, position, completionContext], ExecuteMode.Collect)).filter( + (completion) => completion != null + ); + + let flattenedCompletions = flatten(completions.map((completion) => completion.items)); + const isIncomplete = completions.reduce((incomplete, completion) => incomplete || completion.isIncomplete, false as boolean); + + return CompletionList.create(flattenedCompletions, isIncomplete); + } + + async resolveCompletion(textDocument: TextDocumentIdentifier, completionItem: d.AppCompletionItem): Promise { + const document = this.getDocument(textDocument.uri); + + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + const result = await this.execute('resolveCompletion', [document, completionItem], ExecuteMode.FirstNonNull); + + return result ?? completionItem; + } + + async doTagComplete(textDocument: TextDocumentIdentifier, position: Position): Promise { + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + return this.execute('doTagComplete', [document, position], ExecuteMode.FirstNonNull); + } + + async getFoldingRanges(textDocument: TextDocumentIdentifier): Promise { + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + const foldingRanges = flatten(await this.execute('getFoldingRanges', [document], ExecuteMode.Collect)).filter((completion) => completion != null); + + return foldingRanges; + } + + onWatchFileChanges(onWatchFileChangesParams: any[]): void { + for (const support of this.plugins) { + support.onWatchFileChanges?.(onWatchFileChangesParams); + } + } + + private getDocument(uri: string) { + return this.documentsManager.get(uri); + } + + private execute(name: keyof d.LSProvider, args: any[], mode: ExecuteMode.FirstNonNull): Promise; + private execute(name: keyof d.LSProvider, args: any[], mode: ExecuteMode.Collect): Promise; + private execute(name: keyof d.LSProvider, args: any[], mode: ExecuteMode.None): Promise; + private async execute(name: keyof d.LSProvider, args: any[], mode: ExecuteMode): Promise<(T | null) | T[] | void> { + const plugins = this.plugins.filter((plugin) => typeof plugin[name] === 'function'); + + switch (mode) { + case ExecuteMode.FirstNonNull: + for (const plugin of plugins) { + const res = await this.tryExecutePlugin(plugin, name, args, null); + if (res != null) { + return res; + } } - - const completions = ( - await this.execute( - 'getCompletions', - [document, position, completionContext], - ExecuteMode.Collect - ) - ).filter((completion) => completion != null); - - let flattenedCompletions = flatten(completions.map((completion) => completion.items)); - const isIncomplete = completions.reduce( - (incomplete, completion) => incomplete || completion.isIncomplete, - false as boolean - ); - - return CompletionList.create(flattenedCompletions, isIncomplete); + return null; + case ExecuteMode.Collect: + return Promise.all(plugins.map((plugin) => this.tryExecutePlugin(plugin, name, args, []))); + case ExecuteMode.None: + await Promise.all(plugins.map((plugin) => this.tryExecutePlugin(plugin, name, args, null))); + return; } + } - async resolveCompletion( - textDocument: TextDocumentIdentifier, - completionItem: d.AppCompletionItem - ): Promise { - const document = this.getDocument(textDocument.uri); - - if (!document) { - throw new Error('Cannot call methods on an unopened document'); - } - - const result = await this.execute( - 'resolveCompletion', - [document, completionItem], - ExecuteMode.FirstNonNull - ); - - return result ?? completionItem; - } - - async doTagComplete( - textDocument: TextDocumentIdentifier, - position: Position - ): Promise { - const document = this.getDocument(textDocument.uri); - if (!document) { - throw new Error('Cannot call methods on an unopened document'); - } - - return this.execute( - 'doTagComplete', - [document, position], - ExecuteMode.FirstNonNull - ); - } - - async getFoldingRanges( - textDocument: TextDocumentIdentifier - ): Promise { - const document = this.getDocument(textDocument.uri); - if (!document) { - throw new Error('Cannot call methods on an unopened document'); - } - - const foldingRanges = flatten(await this.execute( - 'getFoldingRanges', - [document], - ExecuteMode.Collect - )).filter((completion) => completion != null) - - return foldingRanges; - } - - onWatchFileChanges(onWatchFileChangesParams: any[]): void { - for (const support of this.plugins) { - support.onWatchFileChanges?.(onWatchFileChangesParams); - } - } - - private getDocument(uri: string) { - return this.documentsManager.get(uri); - } - - private execute( - name: keyof d.LSProvider, - args: any[], - mode: ExecuteMode.FirstNonNull - ): Promise; - private execute( - name: keyof d.LSProvider, - args: any[], - mode: ExecuteMode.Collect - ): Promise; - private execute(name: keyof d.LSProvider, args: any[], mode: ExecuteMode.None): Promise; - private async execute( - name: keyof d.LSProvider, - args: any[], - mode: ExecuteMode - ): Promise<(T | null) | T[] | void> { - const plugins = this.plugins.filter((plugin) => typeof plugin[name] === 'function'); - - switch (mode) { - case ExecuteMode.FirstNonNull: - for (const plugin of plugins) { - const res = await this.tryExecutePlugin(plugin, name, args, null); - if (res != null) { - return res; - } - } - return null; - case ExecuteMode.Collect: - return Promise.all( - plugins.map((plugin) => this.tryExecutePlugin(plugin, name, args, [])) - ); - case ExecuteMode.None: - await Promise.all( - plugins.map((plugin) => this.tryExecutePlugin(plugin, name, args, null)) - ); - return; - } - } - - private async tryExecutePlugin(plugin: any, fnName: string, args: any[], failValue: any) { - try { - return await plugin[fnName](...args); - } catch (e) { - console.error(e); - return failValue; - } + private async tryExecutePlugin(plugin: any, fnName: string, args: any[], failValue: any) { + try { + return await plugin[fnName](...args); + } catch (e) { + console.error(e); + return failValue; } + } } diff --git a/tools/vscode/packages/server/src/plugins/astro/AstroPlugin.ts b/tools/vscode/packages/server/src/plugins/astro/AstroPlugin.ts index 0696504fc..6baf407a5 100644 --- a/tools/vscode/packages/server/src/plugins/astro/AstroPlugin.ts +++ b/tools/vscode/packages/server/src/plugins/astro/AstroPlugin.ts @@ -49,7 +49,7 @@ export class AstroPlugin implements CompletionsProvider, FoldingRangeProvider { endLine: end.line, endCharacter: end.character, kind: FoldingRangeKind.Imports, - } + }, ]; } diff --git a/tools/vscode/packages/server/src/plugins/html/HTMLPlugin.ts b/tools/vscode/packages/server/src/plugins/html/HTMLPlugin.ts index 5114eda1c..7e0ab4861 100644 --- a/tools/vscode/packages/server/src/plugins/html/HTMLPlugin.ts +++ b/tools/vscode/packages/server/src/plugins/html/HTMLPlugin.ts @@ -34,15 +34,7 @@ export class HTMLPlugin implements CompletionsProvider, FoldingRangeProvider { isIncomplete: true, items: [], }; - this.lang.setCompletionParticipants([ - getEmmetCompletionParticipants( - document, - position, - 'html', - this.configManager.getEmmetConfig(), - emmetResults - ) - ]); + this.lang.setCompletionParticipants([getEmmetCompletionParticipants(document, position, 'html', this.configManager.getEmmetConfig(), emmetResults)]); const results = this.lang.doComplete(document, position, html); const items = this.toCompletionItems(results.items); @@ -54,14 +46,13 @@ export class HTMLPlugin implements CompletionsProvider, FoldingRangeProvider { ); } - getFoldingRanges(document: Document): FoldingRange[]|null { + getFoldingRanges(document: Document): FoldingRange[] | null { const html = this.documents.get(document); if (!html) { return null; } return this.lang.getFoldingRanges(document); - } doTagComplete(document: Document, position: Position): string | null { diff --git a/tools/vscode/packages/server/src/plugins/interfaces.ts b/tools/vscode/packages/server/src/plugins/interfaces.ts index 31aafdc3e..b68100de1 100644 --- a/tools/vscode/packages/server/src/plugins/interfaces.ts +++ b/tools/vscode/packages/server/src/plugins/interfaces.ts @@ -1,217 +1,167 @@ +import { CompletionContext, FileChangeType, LinkedEditingRanges, SemanticTokens, SignatureHelpContext, TextDocumentContentChangeEvent } from 'vscode-languageserver'; import { - CompletionContext, - FileChangeType, - LinkedEditingRanges, - SemanticTokens, - SignatureHelpContext, - TextDocumentContentChangeEvent -} from 'vscode-languageserver'; -import { - CodeAction, - CodeActionContext, - Color, - ColorInformation, - ColorPresentation, - CompletionItem, - CompletionList, - DefinitionLink, - Diagnostic, - FormattingOptions, - Hover, - Location, - Position, - Range, - ReferenceContext, - SymbolInformation, - TextDocumentIdentifier, - TextEdit, - WorkspaceEdit, - SelectionRange, - SignatureHelp, - FoldingRange + CodeAction, + CodeActionContext, + Color, + ColorInformation, + ColorPresentation, + CompletionItem, + CompletionList, + DefinitionLink, + Diagnostic, + FormattingOptions, + Hover, + Location, + Position, + Range, + ReferenceContext, + SymbolInformation, + TextDocumentIdentifier, + TextEdit, + WorkspaceEdit, + SelectionRange, + SignatureHelp, + FoldingRange, } from 'vscode-languageserver-types'; import { Document } from '../core/documents'; export type Resolvable = T | Promise; export interface AppCompletionItem extends CompletionItem { - data?: T; + data?: T; } export interface AppCompletionList extends CompletionList { - items: Array>; + items: Array>; } export interface DiagnosticsProvider { - getDiagnostics(document: Document): Resolvable; + getDiagnostics(document: Document): Resolvable; } export interface HoverProvider { - doHover(document: Document, position: Position): Resolvable; + doHover(document: Document, position: Position): Resolvable; } export interface FoldingRangeProvider { - getFoldingRanges(document: Document): Resolvable; + getFoldingRanges(document: Document): Resolvable; } export interface CompletionsProvider { - getCompletions( - document: Document, - position: Position, - completionContext?: CompletionContext - ): Resolvable | null>; + getCompletions(document: Document, position: Position, completionContext?: CompletionContext): Resolvable | null>; - resolveCompletion?( - document: Document, - completionItem: AppCompletionItem - ): Resolvable>; + resolveCompletion?(document: Document, completionItem: AppCompletionItem): Resolvable>; } export interface FormattingProvider { - formatDocument(document: Document, options: FormattingOptions): Resolvable; + formatDocument(document: Document, options: FormattingOptions): Resolvable; } export interface TagCompleteProvider { - doTagComplete(document: Document, position: Position): Resolvable; + doTagComplete(document: Document, position: Position): Resolvable; } export interface DocumentColorsProvider { - getDocumentColors(document: Document): Resolvable; + getDocumentColors(document: Document): Resolvable; } export interface ColorPresentationsProvider { - getColorPresentations( - document: Document, - range: Range, - color: Color - ): Resolvable; + getColorPresentations(document: Document, range: Range, color: Color): Resolvable; } export interface DocumentSymbolsProvider { - getDocumentSymbols(document: Document): Resolvable; + getDocumentSymbols(document: Document): Resolvable; } export interface DefinitionsProvider { - getDefinitions(document: Document, position: Position): Resolvable; + getDefinitions(document: Document, position: Position): Resolvable; } export interface BackwardsCompatibleDefinitionsProvider { - getDefinitions( - document: Document, - position: Position - ): Resolvable; + getDefinitions(document: Document, position: Position): Resolvable; } export interface CodeActionsProvider { - getCodeActions( - document: Document, - range: Range, - context: CodeActionContext - ): Resolvable; - executeCommand?( - document: Document, - command: string, - args?: any[] - ): Resolvable; + getCodeActions(document: Document, range: Range, context: CodeActionContext): Resolvable; + executeCommand?(document: Document, command: string, args?: any[]): Resolvable; } export interface FileRename { - oldUri: string; - newUri: string; + oldUri: string; + newUri: string; } export interface UpdateImportsProvider { - updateImports(fileRename: FileRename): Resolvable; + updateImports(fileRename: FileRename): Resolvable; } export interface RenameProvider { - rename( - document: Document, - position: Position, - newName: string - ): Resolvable; - prepareRename(document: Document, position: Position): Resolvable; + rename(document: Document, position: Position, newName: string): Resolvable; + prepareRename(document: Document, position: Position): Resolvable; } export interface FindReferencesProvider { - findReferences( - document: Document, - position: Position, - context: ReferenceContext - ): Promise; + findReferences(document: Document, position: Position, context: ReferenceContext): Promise; } export interface SignatureHelpProvider { - getSignatureHelp( - document: Document, - position: Position, - context: SignatureHelpContext | undefined - ): Resolvable; + getSignatureHelp(document: Document, position: Position, context: SignatureHelpContext | undefined): Resolvable; } export interface SelectionRangeProvider { - getSelectionRange(document: Document, position: Position): Resolvable; + getSelectionRange(document: Document, position: Position): Resolvable; } export interface SemanticTokensProvider { - getSemanticTokens(textDocument: Document, range?: Range): Resolvable; + getSemanticTokens(textDocument: Document, range?: Range): Resolvable; } export interface LinkedEditingRangesProvider { - getLinkedEditingRanges( - document: Document, - position: Position - ): Resolvable; + getLinkedEditingRanges(document: Document, position: Position): Resolvable; } export interface OnWatchFileChangesPara { - fileName: string; - changeType: FileChangeType; + fileName: string; + changeType: FileChangeType; } export interface OnWatchFileChanges { - onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesPara[]): void; + onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesPara[]): void; } export interface UpdateTsOrJsFile { - updateTsOrJsFile(fileName: string, changes: TextDocumentContentChangeEvent[]): void; + updateTsOrJsFile(fileName: string, changes: TextDocumentContentChangeEvent[]): void; } type ProviderBase = DiagnosticsProvider & - HoverProvider & - CompletionsProvider & - FormattingProvider & - FoldingRangeProvider & - TagCompleteProvider & - DocumentColorsProvider & - ColorPresentationsProvider & - DocumentSymbolsProvider & - UpdateImportsProvider & - CodeActionsProvider & - FindReferencesProvider & - RenameProvider & - SignatureHelpProvider & - SemanticTokensProvider & - LinkedEditingRangesProvider; + HoverProvider & + CompletionsProvider & + FormattingProvider & + FoldingRangeProvider & + TagCompleteProvider & + DocumentColorsProvider & + ColorPresentationsProvider & + DocumentSymbolsProvider & + UpdateImportsProvider & + CodeActionsProvider & + FindReferencesProvider & + RenameProvider & + SignatureHelpProvider & + SemanticTokensProvider & + LinkedEditingRangesProvider; export type LSProvider = ProviderBase & BackwardsCompatibleDefinitionsProvider; export interface LSPProviderConfig { - /** - * Whether or not completion lists that are marked as imcomplete - * should be filtered server side. - */ - filterIncompleteCompletions: boolean; - /** - * Whether or not getDefinitions supports the LocationLink interface. - */ - definitionLinkSupport: boolean; + /** + * Whether or not completion lists that are marked as imcomplete + * should be filtered server side. + */ + filterIncompleteCompletions: boolean; + /** + * Whether or not getDefinitions supports the LocationLink interface. + */ + definitionLinkSupport: boolean; } -export type Plugin = Partial< - ProviderBase & - DefinitionsProvider & - OnWatchFileChanges & - SelectionRangeProvider & - UpdateTsOrJsFile ->; +export type Plugin = Partial; diff --git a/tools/vscode/packages/server/src/plugins/typescript/LanguageServiceManager.ts b/tools/vscode/packages/server/src/plugins/typescript/LanguageServiceManager.ts index 60dec606c..529ab2b4c 100644 --- a/tools/vscode/packages/server/src/plugins/typescript/LanguageServiceManager.ts +++ b/tools/vscode/packages/server/src/plugins/typescript/LanguageServiceManager.ts @@ -38,7 +38,7 @@ export class LanguageServiceManager { const url = urlToPath(curr) as string; if (fileName.startsWith(url) && curr.length < url.length) return url; return found; - }, '') + }, ''); } private createDocument = (fileName: string, content: string) => { diff --git a/tools/vscode/packages/server/src/plugins/typescript/SnapshotManager.ts b/tools/vscode/packages/server/src/plugins/typescript/SnapshotManager.ts index aac26d96e..47d44838d 100644 --- a/tools/vscode/packages/server/src/plugins/typescript/SnapshotManager.ts +++ b/tools/vscode/packages/server/src/plugins/typescript/SnapshotManager.ts @@ -6,328 +6,298 @@ import { pathToUrl } from '../../utils'; import { getScriptKindFromFileName, isAstroFilePath, toVirtualAstroFilePath } from './utils'; export interface TsFilesSpec { - include?: readonly string[]; - exclude?: readonly string[]; + include?: readonly string[]; + exclude?: readonly string[]; } export class SnapshotManager { - private documents: Map = new Map(); - private lastLogged = new Date(new Date().getTime() - 60_001); + private documents: Map = new Map(); + private lastLogged = new Date(new Date().getTime() - 60_001); - private readonly watchExtensions = [ - ts.Extension.Dts, - ts.Extension.Js, - ts.Extension.Jsx, - ts.Extension.Ts, - ts.Extension.Tsx, - ts.Extension.Json - ]; + private readonly watchExtensions = [ts.Extension.Dts, ts.Extension.Js, ts.Extension.Jsx, ts.Extension.Ts, ts.Extension.Tsx, ts.Extension.Json]; - constructor( - private projectFiles: string[], - private fileSpec: TsFilesSpec, - private workspaceRoot: string - ) { - + constructor(private projectFiles: string[], private fileSpec: TsFilesSpec, private workspaceRoot: string) {} + + updateProjectFiles() { + const { include, exclude } = this.fileSpec; + + if (include?.length === 0) return; + + const projectFiles = ts.sys.readDirectory(this.workspaceRoot, this.watchExtensions, exclude, include); + + this.projectFiles = Array.from(new Set([...this.projectFiles, ...projectFiles])); + } + + updateProjectFile(fileName: string, changes?: TextDocumentContentChangeEvent[]): void { + const previousSnapshot = this.get(fileName); + + if (changes) { + if (!(previousSnapshot instanceof TypeScriptDocumentSnapshot)) { + return; + } + previousSnapshot.update(changes); + } else { + const newSnapshot = createDocumentSnapshot(fileName); + + if (previousSnapshot) { + newSnapshot.version = previousSnapshot.version + 1; + } else { + // ensure it's greater than initial version + // so that ts server picks up the change + newSnapshot.version += 1; + } + this.set(fileName, newSnapshot); } + } - updateProjectFiles() { - const { include, exclude } = this.fileSpec; - - if (include?.length === 0) return; + has(fileName: string) { + return this.projectFiles.includes(fileName) || this.getFileNames().includes(fileName); + } - const projectFiles = ts.sys.readDirectory( - this.workspaceRoot, - this.watchExtensions, - exclude, - include - ); + get(fileName: string) { + return this.documents.get(fileName); + } - this.projectFiles = Array.from(new Set([...this.projectFiles, ...projectFiles])); - } - - updateProjectFile(fileName: string, changes?: TextDocumentContentChangeEvent[]): void { - const previousSnapshot = this.get(fileName); - - if (changes) { - if (!(previousSnapshot instanceof TypeScriptDocumentSnapshot)) { - return; - } - previousSnapshot.update(changes); - } else { - const newSnapshot = createDocumentSnapshot(fileName); - - if (previousSnapshot) { - newSnapshot.version = previousSnapshot.version + 1; - } else { - // ensure it's greater than initial version - // so that ts server picks up the change - newSnapshot.version += 1; - } - this.set(fileName, newSnapshot); - } - } - - has(fileName: string) { - return this.projectFiles.includes(fileName) || this.getFileNames().includes(fileName); - } - - get(fileName: string) { - return this.documents.get(fileName); - } - - set(fileName: string, snapshot: DocumentSnapshot) { - // const prev = this.get(fileName); - this.logStatistics(); - return this.documents.set(fileName, snapshot); - } - - delete(fileName: string) { - this.projectFiles = this.projectFiles.filter((s) => s !== fileName); - return this.documents.delete(fileName); - } - - getFileNames() { - return Array.from(this.documents.keys()).map(fileName => toVirtualAstroFilePath(fileName)); - } - - getProjectFileNames() { - return [...this.projectFiles]; - } - - private logStatistics() { - const date = new Date(); - // Don't use setInterval because that will keep tests running forever - if (date.getTime() - this.lastLogged.getTime() > 60_000) { - this.lastLogged = date; - - const projectFiles = this.getProjectFileNames(); - const allFiles = Array.from(new Set([...projectFiles, ...this.getFileNames()])); - console.log( - 'SnapshotManager File Statistics:\n' + - `Project files: ${projectFiles.length}\n` + - `Astro files: ${ - allFiles.filter((name) => name.endsWith('.astro')).length - }\n` + - `From node_modules: ${ - allFiles.filter((name) => name.includes('node_modules')).length - }\n` + - `Total: ${allFiles.length}` - ); - } + set(fileName: string, snapshot: DocumentSnapshot) { + // const prev = this.get(fileName); + this.logStatistics(); + return this.documents.set(fileName, snapshot); + } + + delete(fileName: string) { + this.projectFiles = this.projectFiles.filter((s) => s !== fileName); + return this.documents.delete(fileName); + } + + getFileNames() { + return Array.from(this.documents.keys()).map((fileName) => toVirtualAstroFilePath(fileName)); + } + + getProjectFileNames() { + return [...this.projectFiles]; + } + + private logStatistics() { + const date = new Date(); + // Don't use setInterval because that will keep tests running forever + if (date.getTime() - this.lastLogged.getTime() > 60_000) { + this.lastLogged = date; + + const projectFiles = this.getProjectFileNames(); + const allFiles = Array.from(new Set([...projectFiles, ...this.getFileNames()])); + console.log( + 'SnapshotManager File Statistics:\n' + + `Project files: ${projectFiles.length}\n` + + `Astro files: ${allFiles.filter((name) => name.endsWith('.astro')).length}\n` + + `From node_modules: ${allFiles.filter((name) => name.includes('node_modules')).length}\n` + + `Total: ${allFiles.length}` + ); } + } } export interface DocumentSnapshot extends ts.IScriptSnapshot { - version: number; - filePath: string; - scriptKind: ts.ScriptKind; - positionAt(offset: number): Position; - /** - * Instantiates a source mapper. - * `destroyFragment` needs to be called when - * it's no longer needed / the class should be cleaned up - * in order to prevent memory leaks. - */ - getFragment(): Promise; - /** - * Needs to be called when source mapper - * is no longer needed / the class should be cleaned up - * in order to prevent memory leaks. - */ - destroyFragment(): void; - /** - * Convenience function for getText(0, getLength()) - */ - getFullText(): string; + version: number; + filePath: string; + scriptKind: ts.ScriptKind; + positionAt(offset: number): Position; + /** + * Instantiates a source mapper. + * `destroyFragment` needs to be called when + * it's no longer needed / the class should be cleaned up + * in order to prevent memory leaks. + */ + getFragment(): Promise; + /** + * Needs to be called when source mapper + * is no longer needed / the class should be cleaned up + * in order to prevent memory leaks. + */ + destroyFragment(): void; + /** + * Convenience function for getText(0, getLength()) + */ + getFullText(): string; } export const createDocumentSnapshot = (filePath: string, createDocument?: (_filePath: string, text: string) => Document): DocumentSnapshot => { - const text = ts.sys.readFile(filePath) ?? ''; + const text = ts.sys.readFile(filePath) ?? ''; - if (isAstroFilePath(filePath)) { - if (!createDocument) throw new Error('Astro documents require the "createDocument" utility to be provided'); - const snapshot = new AstroDocumentSnapshot(createDocument(filePath, text)); - return snapshot; - } + if (isAstroFilePath(filePath)) { + if (!createDocument) throw new Error('Astro documents require the "createDocument" utility to be provided'); + const snapshot = new AstroDocumentSnapshot(createDocument(filePath, text)); + return snapshot; + } - return new TypeScriptDocumentSnapshot(0, filePath, text); - -} + return new TypeScriptDocumentSnapshot(0, filePath, text); +}; class AstroDocumentSnapshot implements DocumentSnapshot { - - version = this.doc.version; - scriptKind = ts.ScriptKind.Unknown; - - constructor(private doc: Document) {} + version = this.doc.version; + scriptKind = ts.ScriptKind.Unknown; - async getFragment(): Promise { - return new DocumentFragmentSnapshot(this.doc); - } + constructor(private doc: Document) {} - async destroyFragment() { - return; - } + async getFragment(): Promise { + return new DocumentFragmentSnapshot(this.doc); + } - get text() { - return this.doc.getText(); - } + async destroyFragment() { + return; + } - get filePath() { - return this.doc.getFilePath() || ''; - } + get text() { + return this.doc.getText(); + } - getText(start: number, end: number) { - return this.text.substring(start, end); - } + get filePath() { + return this.doc.getFilePath() || ''; + } - getLength() { - return this.text.length; - } + getText(start: number, end: number) { + return this.text.substring(start, end); + } - getFullText() { - return this.text; - } + getLength() { + return this.text.length; + } - getChangeRange() { - return undefined; - } + getFullText() { + return this.text; + } - positionAt(offset: number) { - return positionAt(offset, this.text); - } + getChangeRange() { + return undefined; + } - getLineContainingOffset(offset: number) { - const chunks = this.getText(0, offset).split('\n'); - return chunks[chunks.length - 1]; - } + positionAt(offset: number) { + return positionAt(offset, this.text); + } - offsetAt(position: Position) { - return offsetAt(position, this.text); - } + getLineContainingOffset(offset: number) { + const chunks = this.getText(0, offset).split('\n'); + return chunks[chunks.length - 1]; + } + offsetAt(position: Position) { + return offsetAt(position, this.text); + } } -class DocumentFragmentSnapshot implements Omit { - - version: number; - filePath: string; - url: string; - text: string; +class DocumentFragmentSnapshot implements Omit { + version: number; + filePath: string; + url: string; + text: string; - scriptKind = ts.ScriptKind.TSX; - scriptInfo = null; + scriptKind = ts.ScriptKind.TSX; + scriptInfo = null; - constructor( - private doc: Document - ) { - const filePath = doc.getFilePath(); - if (!filePath) throw new Error('Cannot create a document fragment from a non-local document'); - const text = doc.getText(); - this.version = doc.version; - this.filePath = toVirtualAstroFilePath(filePath); - this.url = toVirtualAstroFilePath(filePath); - this.text = this.transformContent(text); - } + constructor(private doc: Document) { + const filePath = doc.getFilePath(); + if (!filePath) throw new Error('Cannot create a document fragment from a non-local document'); + const text = doc.getText(); + this.version = doc.version; + this.filePath = toVirtualAstroFilePath(filePath); + this.url = toVirtualAstroFilePath(filePath); + this.text = this.transformContent(text); + } - /** @internal */ - private transformContent(content: string) { - return content.replace(/---/g, '///'); - } + /** @internal */ + private transformContent(content: string) { + return content.replace(/---/g, '///'); + } - getText(start: number, end: number) { - return this.text.substring(start, end); - } + getText(start: number, end: number) { + return this.text.substring(start, end); + } - getLength() { - return this.text.length; - } + getLength() { + return this.text.length; + } - getFullText() { - return this.text; - } + getFullText() { + return this.text; + } - getChangeRange() { - return undefined; - } + getChangeRange() { + return undefined; + } - positionAt(offset: number) { - return positionAt(offset, this.text); - } + positionAt(offset: number) { + return positionAt(offset, this.text); + } - getLineContainingOffset(offset: number) { - const chunks = this.getText(0, offset).split('\n'); - return chunks[chunks.length - 1]; - } + getLineContainingOffset(offset: number) { + const chunks = this.getText(0, offset).split('\n'); + return chunks[chunks.length - 1]; + } - offsetAt(position: Position): number { - return offsetAt(position, this.text); - } + offsetAt(position: Position): number { + return offsetAt(position, this.text); + } } class TypeScriptDocumentSnapshot implements DocumentSnapshot { - - scriptKind = getScriptKindFromFileName(this.filePath); - scriptInfo = null; - url: string; + scriptKind = getScriptKindFromFileName(this.filePath); + scriptInfo = null; + url: string; - - constructor(public version: number, public readonly filePath: string, private text: string) { - this.url = pathToUrl(filePath) + constructor(public version: number, public readonly filePath: string, private text: string) { + this.url = pathToUrl(filePath); + } + + getText(start: number, end: number) { + return this.text.substring(start, end); + } + + getLength() { + return this.text.length; + } + + getFullText() { + return this.text; + } + + getChangeRange() { + return undefined; + } + + positionAt(offset: number) { + return positionAt(offset, this.text); + } + + offsetAt(position: Position): number { + return offsetAt(position, this.text); + } + + async getFragment(): Promise { + return (this as unknown) as any; + } + + destroyFragment() { + // nothing to clean up + } + + getLineContainingOffset(offset: number) { + const chunks = this.getText(0, offset).split('\n'); + return chunks[chunks.length - 1]; + } + + update(changes: TextDocumentContentChangeEvent[]): void { + for (const change of changes) { + let start = 0; + let end = 0; + if ('range' in change) { + start = this.offsetAt(change.range.start); + end = this.offsetAt(change.range.end); + } else { + end = this.getLength(); + } + + this.text = this.text.slice(0, start) + change.text + this.text.slice(end); } - getText(start: number, end: number) { - return this.text.substring(start, end); - } - - getLength() { - return this.text.length; - } - - getFullText() { - return this.text; - } - - getChangeRange() { - return undefined; - } - - positionAt(offset: number) { - return positionAt(offset, this.text); - } - - offsetAt(position: Position): number { - return offsetAt(position, this.text); - } - - async getFragment(): Promise { - return this as unknown as any; - } - - destroyFragment() { - // nothing to clean up - } - - getLineContainingOffset(offset: number) { - const chunks = this.getText(0, offset).split('\n'); - return chunks[chunks.length - 1]; - } - - update(changes: TextDocumentContentChangeEvent[]): void { - for (const change of changes) { - let start = 0; - let end = 0; - if ('range' in change) { - start = this.offsetAt(change.range.start); - end = this.offsetAt(change.range.end); - } else { - end = this.getLength(); - } - - this.text = this.text.slice(0, start) + change.text + this.text.slice(end); - } - - this.version++; - } + this.version++; + } } diff --git a/tools/vscode/packages/server/src/plugins/typescript/TypeScriptPlugin.ts b/tools/vscode/packages/server/src/plugins/typescript/TypeScriptPlugin.ts index 018e8bfda..aab758bdb 100644 --- a/tools/vscode/packages/server/src/plugins/typescript/TypeScriptPlugin.ts +++ b/tools/vscode/packages/server/src/plugins/typescript/TypeScriptPlugin.ts @@ -1,11 +1,7 @@ import type { Document, DocumentManager } from '../../core/documents'; import type { ConfigManager } from '../../core/config'; import type { CompletionsProvider, AppCompletionItem, AppCompletionList } from '../interfaces'; -import { - CompletionContext, - Position, - FileChangeType -} from 'vscode-languageserver'; +import { CompletionContext, Position, FileChangeType } from 'vscode-languageserver'; import * as ts from 'typescript'; import { CompletionsProviderImpl, CompletionEntryWithIdentifer } from './features/CompletionsProvider'; import { LanguageServiceManager } from './LanguageServiceManager'; @@ -13,77 +9,61 @@ import { SnapshotManager } from './SnapshotManager'; import { getScriptKindFromFileName } from './utils'; export class TypeScriptPlugin implements CompletionsProvider { - private readonly docManager: DocumentManager; - private readonly configManager: ConfigManager; - private readonly languageServiceManager: LanguageServiceManager; + private readonly docManager: DocumentManager; + private readonly configManager: ConfigManager; + private readonly languageServiceManager: LanguageServiceManager; - private readonly completionProvider: CompletionsProviderImpl; + private readonly completionProvider: CompletionsProviderImpl; - constructor( - docManager: DocumentManager, - configManager: ConfigManager, - workspaceUris: string[] - ) { - this.docManager = docManager; - this.configManager = configManager; - this.languageServiceManager = new LanguageServiceManager(docManager, configManager, workspaceUris); - - this.completionProvider = new CompletionsProviderImpl(this.languageServiceManager); - } + constructor(docManager: DocumentManager, configManager: ConfigManager, workspaceUris: string[]) { + this.docManager = docManager; + this.configManager = configManager; + this.languageServiceManager = new LanguageServiceManager(docManager, configManager, workspaceUris); - async getCompletions( - document: Document, - position: Position, - completionContext?: CompletionContext - ): Promise | null> { - const completions = await this.completionProvider.getCompletions( - document, - position, - completionContext - ); + this.completionProvider = new CompletionsProviderImpl(this.languageServiceManager); + } - return completions; - } + async getCompletions(document: Document, position: Position, completionContext?: CompletionContext): Promise | null> { + const completions = await this.completionProvider.getCompletions(document, position, completionContext); - async resolveCompletion( - document: Document, - completionItem: AppCompletionItem - ): Promise> { - return this.completionProvider.resolveCompletion(document, completionItem); - } + return completions; + } - async onWatchFileChanges(onWatchFileChangesParams: any[]): Promise { - const doneUpdateProjectFiles = new Set(); + async resolveCompletion(document: Document, completionItem: AppCompletionItem): Promise> { + return this.completionProvider.resolveCompletion(document, completionItem); + } - for (const { fileName, changeType } of onWatchFileChangesParams) { - const scriptKind = getScriptKindFromFileName(fileName); + async onWatchFileChanges(onWatchFileChangesParams: any[]): Promise { + const doneUpdateProjectFiles = new Set(); - if (scriptKind === ts.ScriptKind.Unknown) { - // We don't deal with svelte files here - continue; - } + for (const { fileName, changeType } of onWatchFileChangesParams) { + const scriptKind = getScriptKindFromFileName(fileName); - const snapshotManager = await this.getSnapshotManager(fileName); - if (changeType === FileChangeType.Created) { - if (!doneUpdateProjectFiles.has(snapshotManager)) { - snapshotManager.updateProjectFiles(); - doneUpdateProjectFiles.add(snapshotManager); - } - } else if (changeType === FileChangeType.Deleted) { - snapshotManager.delete(fileName); - return; - } + if (scriptKind === ts.ScriptKind.Unknown) { + // We don't deal with svelte files here + continue; + } - snapshotManager.updateProjectFile(fileName); + const snapshotManager = await this.getSnapshotManager(fileName); + if (changeType === FileChangeType.Created) { + if (!doneUpdateProjectFiles.has(snapshotManager)) { + snapshotManager.updateProjectFiles(); + doneUpdateProjectFiles.add(snapshotManager); } - } + } else if (changeType === FileChangeType.Deleted) { + snapshotManager.delete(fileName); + return; + } - /** - * - * @internal - */ - public async getSnapshotManager(fileName: string) { - return this.languageServiceManager.getSnapshotManager(fileName); + snapshotManager.updateProjectFile(fileName); } + } + + /** + * + * @internal + */ + public async getSnapshotManager(fileName: string) { + return this.languageServiceManager.getSnapshotManager(fileName); + } } - diff --git a/tools/vscode/packages/server/src/plugins/typescript/astro-sys.ts b/tools/vscode/packages/server/src/plugins/typescript/astro-sys.ts index 0459528c5..36d009eb6 100644 --- a/tools/vscode/packages/server/src/plugins/typescript/astro-sys.ts +++ b/tools/vscode/packages/server/src/plugins/typescript/astro-sys.ts @@ -6,37 +6,37 @@ import { ensureRealAstroFilePath, isAstroFilePath, isVirtualAstroFilePath, toRea * This should only be accessed by TS Astro module resolution. */ export function createAstroSys(getSnapshot: (fileName: string) => DocumentSnapshot) { - const AstroSys: ts.System = { - ...ts.sys, - fileExists(path: string) { - if (isAstroFilePath(path) || isVirtualAstroFilePath(path)) { - console.log('fileExists', path, ts.sys.fileExists(ensureRealAstroFilePath(path))); - } - return ts.sys.fileExists(ensureRealAstroFilePath(path)); - }, - readFile(path: string) { - if (isAstroFilePath(path) || isVirtualAstroFilePath(path)) { - console.log('readFile', path); - } - const snapshot = getSnapshot(path); - return snapshot.getFullText(); - }, - readDirectory(path, extensions, exclude, include, depth) { - const extensionsWithAstro = (extensions ?? []).concat(...['.astro']); - const result = ts.sys.readDirectory(path, extensionsWithAstro, exclude, include, depth);; - return result; - } + const AstroSys: ts.System = { + ...ts.sys, + fileExists(path: string) { + if (isAstroFilePath(path) || isVirtualAstroFilePath(path)) { + console.log('fileExists', path, ts.sys.fileExists(ensureRealAstroFilePath(path))); + } + return ts.sys.fileExists(ensureRealAstroFilePath(path)); + }, + readFile(path: string) { + if (isAstroFilePath(path) || isVirtualAstroFilePath(path)) { + console.log('readFile', path); + } + const snapshot = getSnapshot(path); + return snapshot.getFullText(); + }, + readDirectory(path, extensions, exclude, include, depth) { + const extensionsWithAstro = (extensions ?? []).concat(...['.astro']); + const result = ts.sys.readDirectory(path, extensionsWithAstro, exclude, include, depth); + return result; + }, + }; + + if (ts.sys.realpath) { + const realpath = ts.sys.realpath; + AstroSys.realpath = function (path) { + if (isVirtualAstroFilePath(path)) { + return realpath(toRealAstroFilePath(path)) + '.ts'; + } + return realpath(path); }; + } - if (ts.sys.realpath) { - const realpath = ts.sys.realpath; - AstroSys.realpath = function (path) { - if (isVirtualAstroFilePath(path)) { - return realpath(toRealAstroFilePath(path)) + '.ts'; - } - return realpath(path); - }; - } - - return AstroSys; + return AstroSys; } diff --git a/tools/vscode/packages/server/src/plugins/typescript/features/CompletionsProvider.ts b/tools/vscode/packages/server/src/plugins/typescript/features/CompletionsProvider.ts index ebbc16e31..348f3e4ae 100644 --- a/tools/vscode/packages/server/src/plugins/typescript/features/CompletionsProvider.ts +++ b/tools/vscode/packages/server/src/plugins/typescript/features/CompletionsProvider.ts @@ -99,7 +99,7 @@ export class CompletionsProviderImpl implements CompletionsProvider `${projectVersion}`, getScriptFileNames: () => Array.from(new Set([...snapshotManager.getFileNames(), ...snapshotManager.getProjectFileNames()])), getScriptSnapshot, - getScriptVersion: (fileName: string) => getScriptSnapshot(fileName).version.toString() + getScriptVersion: (fileName: string) => getScriptSnapshot(fileName).version.toString(), }; const languageService = ts.createLanguageService(host); const languageServiceProxy = new Proxy(languageService, { get(target, prop) { return Reflect.get(target, prop); - } - }) + }, + }); return { tsconfigPath, @@ -141,19 +133,16 @@ async function createLanguageService(tsconfigPath: string, workspaceRoot: string } function getScriptSnapshot(fileName: string): DocumentSnapshot { - fileName = ensureRealAstroFilePath(fileName); + fileName = ensureRealAstroFilePath(fileName); - let doc = snapshotManager.get(fileName); - if (doc) { - return doc; - } - - doc = createDocumentSnapshot( - fileName, - docContext.createDocument, - ); - snapshotManager.set(fileName, doc); + let doc = snapshotManager.get(fileName); + if (doc) { return doc; + } + + doc = createDocumentSnapshot(fileName, docContext.createDocument); + snapshotManager.set(fileName, doc); + return doc; } } @@ -168,7 +157,7 @@ function getDefaultJsConfig(): { compilerOptions: { maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, - allowJs: true + allowJs: true, }, include: ['astro'], }; diff --git a/tools/vscode/packages/server/src/plugins/typescript/utils.ts b/tools/vscode/packages/server/src/plugins/typescript/utils.ts index 058868474..1f42e7d0a 100644 --- a/tools/vscode/packages/server/src/plugins/typescript/utils.ts +++ b/tools/vscode/packages/server/src/plugins/typescript/utils.ts @@ -3,180 +3,172 @@ import { CompletionItemKind, DiagnosticSeverity } from 'vscode-languageserver'; import { dirname } from 'path'; import { pathToUrl } from '../../utils'; -export function scriptElementKindToCompletionItemKind( - kind: ts.ScriptElementKind -): CompletionItemKind { - switch (kind) { - case ts.ScriptElementKind.primitiveType: - case ts.ScriptElementKind.keyword: - return CompletionItemKind.Keyword; - case ts.ScriptElementKind.constElement: - return CompletionItemKind.Constant; - case ts.ScriptElementKind.letElement: - case ts.ScriptElementKind.variableElement: - case ts.ScriptElementKind.localVariableElement: - case ts.ScriptElementKind.alias: - return CompletionItemKind.Variable; - case ts.ScriptElementKind.memberVariableElement: - case ts.ScriptElementKind.memberGetAccessorElement: - case ts.ScriptElementKind.memberSetAccessorElement: - return CompletionItemKind.Field; - case ts.ScriptElementKind.functionElement: - return CompletionItemKind.Function; - case ts.ScriptElementKind.memberFunctionElement: - case ts.ScriptElementKind.constructSignatureElement: - case ts.ScriptElementKind.callSignatureElement: - case ts.ScriptElementKind.indexSignatureElement: - return CompletionItemKind.Method; - case ts.ScriptElementKind.enumElement: - return CompletionItemKind.Enum; - case ts.ScriptElementKind.moduleElement: - case ts.ScriptElementKind.externalModuleName: - return CompletionItemKind.Module; - case ts.ScriptElementKind.classElement: - case ts.ScriptElementKind.typeElement: - return CompletionItemKind.Class; - case ts.ScriptElementKind.interfaceElement: - return CompletionItemKind.Interface; - case ts.ScriptElementKind.warning: - case ts.ScriptElementKind.scriptElement: - return CompletionItemKind.File; - case ts.ScriptElementKind.directory: - return CompletionItemKind.Folder; - case ts.ScriptElementKind.string: - return CompletionItemKind.Constant; - } - return CompletionItemKind.Property; +export function scriptElementKindToCompletionItemKind(kind: ts.ScriptElementKind): CompletionItemKind { + switch (kind) { + case ts.ScriptElementKind.primitiveType: + case ts.ScriptElementKind.keyword: + return CompletionItemKind.Keyword; + case ts.ScriptElementKind.constElement: + return CompletionItemKind.Constant; + case ts.ScriptElementKind.letElement: + case ts.ScriptElementKind.variableElement: + case ts.ScriptElementKind.localVariableElement: + case ts.ScriptElementKind.alias: + return CompletionItemKind.Variable; + case ts.ScriptElementKind.memberVariableElement: + case ts.ScriptElementKind.memberGetAccessorElement: + case ts.ScriptElementKind.memberSetAccessorElement: + return CompletionItemKind.Field; + case ts.ScriptElementKind.functionElement: + return CompletionItemKind.Function; + case ts.ScriptElementKind.memberFunctionElement: + case ts.ScriptElementKind.constructSignatureElement: + case ts.ScriptElementKind.callSignatureElement: + case ts.ScriptElementKind.indexSignatureElement: + return CompletionItemKind.Method; + case ts.ScriptElementKind.enumElement: + return CompletionItemKind.Enum; + case ts.ScriptElementKind.moduleElement: + case ts.ScriptElementKind.externalModuleName: + return CompletionItemKind.Module; + case ts.ScriptElementKind.classElement: + case ts.ScriptElementKind.typeElement: + return CompletionItemKind.Class; + case ts.ScriptElementKind.interfaceElement: + return CompletionItemKind.Interface; + case ts.ScriptElementKind.warning: + case ts.ScriptElementKind.scriptElement: + return CompletionItemKind.File; + case ts.ScriptElementKind.directory: + return CompletionItemKind.Folder; + case ts.ScriptElementKind.string: + return CompletionItemKind.Constant; + } + return CompletionItemKind.Property; } -export function getCommitCharactersForScriptElement( - kind: ts.ScriptElementKind -): string[] | undefined { - const commitCharacters: string[] = []; - switch (kind) { - case ts.ScriptElementKind.memberGetAccessorElement: - case ts.ScriptElementKind.memberSetAccessorElement: - case ts.ScriptElementKind.constructSignatureElement: - case ts.ScriptElementKind.callSignatureElement: - case ts.ScriptElementKind.indexSignatureElement: - case ts.ScriptElementKind.enumElement: - case ts.ScriptElementKind.interfaceElement: - commitCharacters.push('.'); - break; +export function getCommitCharactersForScriptElement(kind: ts.ScriptElementKind): string[] | undefined { + const commitCharacters: string[] = []; + switch (kind) { + case ts.ScriptElementKind.memberGetAccessorElement: + case ts.ScriptElementKind.memberSetAccessorElement: + case ts.ScriptElementKind.constructSignatureElement: + case ts.ScriptElementKind.callSignatureElement: + case ts.ScriptElementKind.indexSignatureElement: + case ts.ScriptElementKind.enumElement: + case ts.ScriptElementKind.interfaceElement: + commitCharacters.push('.'); + break; - case ts.ScriptElementKind.moduleElement: - case ts.ScriptElementKind.alias: - case ts.ScriptElementKind.constElement: - case ts.ScriptElementKind.letElement: - case ts.ScriptElementKind.variableElement: - case ts.ScriptElementKind.localVariableElement: - case ts.ScriptElementKind.memberVariableElement: - case ts.ScriptElementKind.classElement: - case ts.ScriptElementKind.functionElement: - case ts.ScriptElementKind.memberFunctionElement: - commitCharacters.push('.', ','); - commitCharacters.push('('); - break; - } + case ts.ScriptElementKind.moduleElement: + case ts.ScriptElementKind.alias: + case ts.ScriptElementKind.constElement: + case ts.ScriptElementKind.letElement: + case ts.ScriptElementKind.variableElement: + case ts.ScriptElementKind.localVariableElement: + case ts.ScriptElementKind.memberVariableElement: + case ts.ScriptElementKind.classElement: + case ts.ScriptElementKind.functionElement: + case ts.ScriptElementKind.memberFunctionElement: + commitCharacters.push('.', ','); + commitCharacters.push('('); + break; + } - return commitCharacters.length === 0 ? undefined : commitCharacters; + return commitCharacters.length === 0 ? undefined : commitCharacters; } export function mapSeverity(category: ts.DiagnosticCategory): DiagnosticSeverity { - switch (category) { - case ts.DiagnosticCategory.Error: - return DiagnosticSeverity.Error; - case ts.DiagnosticCategory.Warning: - return DiagnosticSeverity.Warning; - case ts.DiagnosticCategory.Suggestion: - return DiagnosticSeverity.Hint; - case ts.DiagnosticCategory.Message: - return DiagnosticSeverity.Information; - } + switch (category) { + case ts.DiagnosticCategory.Error: + return DiagnosticSeverity.Error; + case ts.DiagnosticCategory.Warning: + return DiagnosticSeverity.Warning; + case ts.DiagnosticCategory.Suggestion: + return DiagnosticSeverity.Hint; + case ts.DiagnosticCategory.Message: + return DiagnosticSeverity.Information; + } - return DiagnosticSeverity.Error; + return DiagnosticSeverity.Error; } export function getScriptKindFromFileName(fileName: string): ts.ScriptKind { - const ext = fileName.substr(fileName.lastIndexOf('.')); - switch (ext.toLowerCase()) { - case ts.Extension.Js: - return ts.ScriptKind.JS; - case ts.Extension.Jsx: - return ts.ScriptKind.JSX; - case ts.Extension.Ts: - return ts.ScriptKind.TS; - case ts.Extension.Tsx: - return ts.ScriptKind.TSX; - case ts.Extension.Json: - return ts.ScriptKind.JSON; - default: - return ts.ScriptKind.Unknown; - } + const ext = fileName.substr(fileName.lastIndexOf('.')); + switch (ext.toLowerCase()) { + case ts.Extension.Js: + return ts.ScriptKind.JS; + case ts.Extension.Jsx: + return ts.ScriptKind.JSX; + case ts.Extension.Ts: + return ts.ScriptKind.TS; + case ts.Extension.Tsx: + return ts.ScriptKind.TSX; + case ts.Extension.Json: + return ts.ScriptKind.JSON; + default: + return ts.ScriptKind.Unknown; + } } export function isAstroFilePath(filePath: string) { - return filePath.endsWith('.astro'); + return filePath.endsWith('.astro'); } export function isVirtualAstroFilePath(filePath: string) { - return filePath.endsWith('.astro.ts'); + return filePath.endsWith('.astro.ts'); } export function toVirtualAstroFilePath(filePath: string) { - return `${filePath}.ts`; + return `${filePath}.ts`; } export function toRealAstroFilePath(filePath: string) { - return filePath.slice(0, -'.ts'.length); + return filePath.slice(0, -'.ts'.length); } export function ensureRealAstroFilePath(filePath: string) { - return isVirtualAstroFilePath(filePath) ? toRealAstroFilePath(filePath) : filePath; + return isVirtualAstroFilePath(filePath) ? toRealAstroFilePath(filePath) : filePath; } export function findTsConfigPath(fileName: string, rootUris: string[]) { - const searchDir = dirname(fileName); - const path = - ts.findConfigFile(searchDir, ts.sys.fileExists, 'tsconfig.json') || - ts.findConfigFile(searchDir, ts.sys.fileExists, 'jsconfig.json') || - ''; - // Don't return config files that exceed the current workspace context. - return !!path && rootUris.some((rootUri) => isSubPath(rootUri, path)) ? path : ''; + const searchDir = dirname(fileName); + const path = ts.findConfigFile(searchDir, ts.sys.fileExists, 'tsconfig.json') || ts.findConfigFile(searchDir, ts.sys.fileExists, 'jsconfig.json') || ''; + // Don't return config files that exceed the current workspace context. + return !!path && rootUris.some((rootUri) => isSubPath(rootUri, path)) ? path : ''; } /** */ export function isSubPath(uri: string, possibleSubPath: string): boolean { - return pathToUrl(possibleSubPath).startsWith(uri); + return pathToUrl(possibleSubPath).startsWith(uri); } - /** Substitutes */ export function substituteWithWhitespace(result: string, start: number, end: number, oldContent: string, before: string, after: string) { - let accumulatedWS = 0; - result += before; - for (let i = start + before.length; i < end; i++) { - let ch = oldContent[i]; - if (ch === '\n' || ch === '\r') { - // only write new lines, skip the whitespace - accumulatedWS = 0; - result += ch; - } else { - accumulatedWS++; - } - } - result = append(result, ' ', accumulatedWS - after.length); - result += after; - return result; + let accumulatedWS = 0; + result += before; + for (let i = start + before.length; i < end; i++) { + let ch = oldContent[i]; + if (ch === '\n' || ch === '\r') { + // only write new lines, skip the whitespace + accumulatedWS = 0; + result += ch; + } else { + accumulatedWS++; + } + } + result = append(result, ' ', accumulatedWS - after.length); + result += after; + return result; } function append(result: string, str: string, n: number): string { - while (n > 0) { - if (n & 1) { - result += str; - } - n >>= 1; - str += str; - } - return result; + while (n > 0) { + if (n & 1) { + result += str; + } + n >>= 1; + str += str; + } + return result; } diff --git a/tools/vscode/packages/server/src/utils.ts b/tools/vscode/packages/server/src/utils.ts index c764aae13..f9f1acf34 100644 --- a/tools/vscode/packages/server/src/utils.ts +++ b/tools/vscode/packages/server/src/utils.ts @@ -4,68 +4,61 @@ import { Node } from 'vscode-html-languageservice'; /** Normalizes a document URI */ export function normalizeUri(uri: string): string { - return URI.parse(uri).toString(); + return URI.parse(uri).toString(); } /** Turns a URL into a normalized FS Path */ export function urlToPath(stringUrl: string): string | null { - const url = URI.parse(stringUrl); - if (url.scheme !== 'file') { - return null; - } - return url.fsPath.replace(/\\/g, '/'); + const url = URI.parse(stringUrl); + if (url.scheme !== 'file') { + return null; + } + return url.fsPath.replace(/\\/g, '/'); } /** Converts a path to a URL */ export function pathToUrl(path: string) { - return URI.file(path).toString(); + return URI.file(path).toString(); } - /** -* -* The language service is case insensitive, and would provide -* hover info for Svelte components like `Option` which have -* the same name like a html tag. -*/ + * + * The language service is case insensitive, and would provide + * hover info for Svelte components like `Option` which have + * the same name like a html tag. + */ export function isPossibleComponent(node: Node): boolean { - return !!node.tag?.[0].match(/[A-Z]/); + return !!node.tag?.[0].match(/[A-Z]/); } /** -* -* The language service is case insensitive, and would provide -* hover info for Svelte components like `Option` which have -* the same name like a html tag. -*/ + * + * The language service is case insensitive, and would provide + * hover info for Svelte components like `Option` which have + * the same name like a html tag. + */ export function isPossibleClientComponent(node: Node): boolean { - return isPossibleComponent(node) && (node.tag?.indexOf(':') ?? -1) > -1; + return isPossibleComponent(node) && (node.tag?.indexOf(':') ?? -1) > -1; } /** Flattens an array */ export function flatten(arr: T[][]): T[] { - return arr.reduce((all, item) => [...all, ...item], []); + return arr.reduce((all, item) => [...all, ...item], []); } /** Clamps a number between min and max */ export function clamp(num: number, min: number, max: number): number { - return Math.max(min, Math.min(max, num)); + return Math.max(min, Math.min(max, num)); } /** Checks if a position is inside range */ export function isInRange(positionToTest: Position, range: Range): boolean { - return ( - isBeforeOrEqualToPosition(range.end, positionToTest) && - isBeforeOrEqualToPosition(positionToTest, range.start) - ); + return isBeforeOrEqualToPosition(range.end, positionToTest) && isBeforeOrEqualToPosition(positionToTest, range.start); } /** */ export function isBeforeOrEqualToPosition(position: Position, positionToTest: Position): boolean { - return ( - positionToTest.line < position.line || - (positionToTest.line === position.line && positionToTest.character <= position.character) - ); + return positionToTest.line < position.line || (positionToTest.line === position.line && positionToTest.character <= position.character); } /** @@ -76,23 +69,19 @@ export function isBeforeOrEqualToPosition(position: Position, positionToTest: Po * @param determineIfSame The function which determines if the previous invocation should be canceld or not * @param miliseconds Number of miliseconds to debounce */ -export function debounceSameArg( - fn: (arg: T) => void, - shouldCancelPrevious: (newArg: T, prevArg?: T) => boolean, - miliseconds: number -): (arg: T) => void { - let timeout: any; - let prevArg: T | undefined; +export function debounceSameArg(fn: (arg: T) => void, shouldCancelPrevious: (newArg: T, prevArg?: T) => boolean, miliseconds: number): (arg: T) => void { + let timeout: any; + let prevArg: T | undefined; - return (arg: T) => { - if (shouldCancelPrevious(arg, prevArg)) { - clearTimeout(timeout); - } + return (arg: T) => { + if (shouldCancelPrevious(arg, prevArg)) { + clearTimeout(timeout); + } - prevArg = arg; - timeout = setTimeout(() => { - fn(arg); - prevArg = undefined; - }, miliseconds); - }; + prevArg = arg; + timeout = setTimeout(() => { + fn(arg); + prevArg = undefined; + }, miliseconds); + }; } diff --git a/tools/vscode/packages/server/tsconfig.json b/tools/vscode/packages/server/tsconfig.json index 6af42d0b2..7b2ff1ea2 100644 --- a/tools/vscode/packages/server/tsconfig.json +++ b/tools/vscode/packages/server/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - }, - "include": ["src"], - "exclude": ["node_modules"], + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules"] } diff --git a/tools/vscode/syntaxes/astro.tmLanguage.json b/tools/vscode/syntaxes/astro.tmLanguage.json index 9c09d0e71..225b023ca 100644 --- a/tools/vscode/syntaxes/astro.tmLanguage.json +++ b/tools/vscode/syntaxes/astro.tmLanguage.json @@ -1,704 +1,706 @@ { - "fileTypes": [ - "astro" - ], - "foldingStartMarker": "(?x)\n(<(?i:head|body|table|thead|tbody|tfoot|tr|div|select|fieldset|style|script|ul|ol|li|form|dl)\\b.*?>\n|)$\n|<\\?(?:php)?.*\\b(if|for(each)?|while)\\b.+:\n|\\{\\{?(if|foreach|capture|literal|foreach|php|section|strip)\n|\\{\\s*($|\\?>\\s*$|\/\/|\/\\*(.*\\*\/\\s*$|(?!.*?\\*\/)))\n)", - "foldingStopMarker": "(?x)\n(<\/(?i:head|body|table|thead|tbody|tfoot|tr|div|select|fieldset|style|script|ul|ol|li|form|dl)>\n|^(?!.*?$\n|<\\?(?:php)?.*\\bend(if|for(each)?|while)\\b\n|\\{\\{?\/(if|foreach|capture|literal|foreach|php|section|strip)\n|^[^{]*\\}\n)", - "keyEquivalent": "^~H", - "name": "Astro", - "patterns": [ - { - "include": "#astro-expressions" - }, - { - "begin": "(<)([a-zA-Z0-9:-]++)(?=[^>]*><\/\\2>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": "(>)(<)(\/)(\\2)(>)", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.end.html" - }, - "2": { - "name": "punctuation.definition.tag.begin.html meta.scope.between-tag-pair.html" - }, - "3": { - "name": "punctuation.definition.tag.begin.html" - }, - "4": { - "name": "entity.name.tag.html" - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.any.html", - "patterns": [ - { - "include": "#tag-stuff" - } - ] - }, - { - "begin": "(<\\?)(xml)", - "captures": { - "1": { - "name": "punctuation.definition.tag.html" - }, - "2": { - "name": "entity.name.tag.xml.html" - } - }, - "end": "(\\?>)", - "name": "meta.tag.preprocessor.xml.html", - "patterns": [ - { - "include": "#tag-generic-attribute" - }, - { - "include": "#string-double-quoted" - }, - { - "include": "#string-single-quoted" - } - ] - }, - { - "begin": ")$\n|<\\?(?:php)?.*\\b(if|for(each)?|while)\\b.+:\n|\\{\\{?(if|foreach|capture|literal|foreach|php|section|strip)\n|\\{\\s*($|\\?>\\s*$|//|/\\*(.*\\*/\\s*$|(?!.*?\\*/)))\n)", + "foldingStopMarker": "(?x)\n(\n|^(?!.*?$\n|<\\?(?:php)?.*\\bend(if|for(each)?|while)\\b\n|\\{\\{?/(if|foreach|capture|literal|foreach|php|section|strip)\n|^[^{]*\\}\n)", + "keyEquivalent": "^~H", + "name": "Astro", + "patterns": [ + { + "include": "#astro-expressions" + }, + { + "begin": "(<)([a-zA-Z0-9:-]++)(?=[^>]*>)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.tag.begin.html" + }, + "2": { + "name": "entity.name.tag.html" + } + }, + "end": "(>)(<)(/)(\\2)(>)", + "endCaptures": { + "1": { + "name": "punctuation.definition.tag.end.html" + }, + "2": { + "name": "punctuation.definition.tag.begin.html meta.scope.between-tag-pair.html" + }, + "3": { + "name": "punctuation.definition.tag.begin.html" + }, + "4": { + "name": "entity.name.tag.html" + }, + "5": { + "name": "punctuation.definition.tag.end.html" + } + }, + "name": "meta.tag.any.html", + "patterns": [ + { + "include": "#tag-stuff" + } + ] + }, + { + "begin": "(<\\?)(xml)", + "captures": { + "1": { + "name": "punctuation.definition.tag.html" + }, + "2": { + "name": "entity.name.tag.xml.html" + } + }, + "end": "(\\?>)", + "name": "meta.tag.preprocessor.xml.html", + "patterns": [ + { + "include": "#tag-generic-attribute" + }, + { + "include": "#string-double-quoted" + }, + { + "include": "#string-single-quoted" + } + ] + }, + { + "begin": "