diff --git a/packages/addons/eslint/index.ts b/packages/addons/eslint/index.ts index 757a4fb6d..2a671fc54 100644 --- a/packages/addons/eslint/index.ts +++ b/packages/addons/eslint/index.ts @@ -53,7 +53,7 @@ export default defineAddon({ }); sv.file(files.eslintConfig, (content) => { - const { ast, generateCode } = parseScript(content); + const { ast, comments, generateCode } = parseScript(content); const eslintConfigs: Array = []; imports.addDefault(ast, { from: './svelte.config.js', as: 'svelteConfig' }); @@ -85,18 +85,16 @@ export default defineAddon({ if (rules.properties[0].type !== 'Property') { throw new Error('rules.properties[0].type !== "Property"'); } - rules.properties[0].key.leadingComments = [ - { - type: 'Line', - value: - ' typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.' - }, - { - type: 'Line', - value: - ' see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors' - } - ]; + comments.add(rules.properties[0].key, { + type: 'Line', + value: + ' typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.' + }); + comments.add(rules.properties[0].key, { + type: 'Line', + value: + ' see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors' + }); const globalsConfig = object.create({ languageOptions: { @@ -153,7 +151,9 @@ export default defineAddon({ // type annotate config if (!typescript) - common.addJsDocTypeComment(astNode, { type: "import('eslint').Linter.Config[]" }); + common.addJsDocTypeComment(astNode, comments, { + type: "import('eslint').Linter.Config[]" + }); if (typescript) imports.addDefault(ast, { from: 'typescript-eslint', as: 'ts' }); imports.addDefault(ast, { from: 'globals', as: 'globals' }); diff --git a/packages/addons/sveltekit-adapter/index.ts b/packages/addons/sveltekit-adapter/index.ts index 301fd654a..3806668c4 100644 --- a/packages/addons/sveltekit-adapter/index.ts +++ b/packages/addons/sveltekit-adapter/index.ts @@ -49,7 +49,7 @@ export default defineAddon({ sv.devDependency(adapter.package, adapter.version); sv.file(files.svelteConfig, (content) => { - const { ast, generateCode } = parseScript(content); + const { ast, comments, generateCode } = parseScript(content); // finds any existing adapter's import declaration const importDecls = ast.body.filter((n) => n.type === 'ImportDeclaration'); @@ -86,8 +86,15 @@ export default defineAddon({ if (adapter.package !== '@sveltejs/adapter-auto') { const fallback = object.create({}); const cfgKitValue = object.property(config, { name: 'kit', fallback }); - const cfgAdapter = object.propertyNode(cfgKitValue, { name: 'adapter', fallback }); - cfgAdapter.leadingComments = []; + + // removes any existing adapter auto comments + comments.remove( + (c) => + c.loc && + cfgKitValue.loc && + c.loc.start.line >= cfgKitValue.loc.start.line && + c.loc.end.line <= cfgKitValue.loc.end.line + ); } return generateCode(); diff --git a/packages/cli/tests/snapshots/create-with-all-addons/playwright.config.ts b/packages/cli/tests/snapshots/create-with-all-addons/playwright.config.ts index f6c81af8a..8f5062c2f 100644 --- a/packages/cli/tests/snapshots/create-with-all-addons/playwright.config.ts +++ b/packages/cli/tests/snapshots/create-with-all-addons/playwright.config.ts @@ -1,9 +1,6 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ - webServer: { - command: 'npm run build && npm run preview', - port: 4173 - }, + webServer: { command: 'npm run build && npm run preview', port: 4173 }, testDir: 'e2e' }); diff --git a/packages/cli/tests/snapshots/create-with-all-addons/src/app.d.ts b/packages/cli/tests/snapshots/create-with-all-addons/src/app.d.ts index a6c98c5eb..bb1c423bb 100644 --- a/packages/cli/tests/snapshots/create-with-all-addons/src/app.d.ts +++ b/packages/cli/tests/snapshots/create-with-all-addons/src/app.d.ts @@ -6,10 +6,13 @@ declare global { user: import('$lib/server/auth').SessionValidationResult['user']; session: import('$lib/server/auth').SessionValidationResult['session'] } - } // interface Error {} - // interface Locals {} -} // interface PageData {} -// interface PageState {} -// interface Platform {} + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + export {}; diff --git a/packages/cli/tests/snapshots/create-with-all-addons/src/hooks.server.ts b/packages/cli/tests/snapshots/create-with-all-addons/src/hooks.server.ts index 48874a52b..2fab5d931 100644 --- a/packages/cli/tests/snapshots/create-with-all-addons/src/hooks.server.ts +++ b/packages/cli/tests/snapshots/create-with-all-addons/src/hooks.server.ts @@ -17,6 +17,7 @@ const handleAuth: Handle = async ({ event, resolve }) => { if (!sessionToken) { event.locals.user = null; event.locals.session = null; + return resolve(event); } @@ -30,6 +31,7 @@ const handleAuth: Handle = async ({ event, resolve }) => { event.locals.user = user; event.locals.session = session; + return resolve(event); }; diff --git a/packages/cli/tests/snapshots/create-with-all-addons/svelte.config.js b/packages/cli/tests/snapshots/create-with-all-addons/svelte.config.js index ad0e153f2..f59ea15cb 100644 --- a/packages/cli/tests/snapshots/create-with-all-addons/svelte.config.js +++ b/packages/cli/tests/snapshots/create-with-all-addons/svelte.config.js @@ -7,6 +7,7 @@ const config = { // Consult https://svelte.dev/docs/kit/integrations // for more information about preprocessors preprocess: [vitePreprocess(), mdsvex()], + kit: { adapter: adapter() }, extensions: ['.svelte', '.svx'] }; diff --git a/packages/cli/tests/snapshots/create-with-all-addons/vite.config.ts b/packages/cli/tests/snapshots/create-with-all-addons/vite.config.ts index 9439a7058..6f612e940 100644 --- a/packages/cli/tests/snapshots/create-with-all-addons/vite.config.ts +++ b/packages/cli/tests/snapshots/create-with-all-addons/vite.config.ts @@ -10,29 +10,33 @@ export default defineConfig({ tailwindcss(), sveltekit(), devtoolsJson(), - paraglideVitePlugin({ - project: './project.inlang', - outdir: './src/lib/paraglide' - }) + paraglideVitePlugin({ project: './project.inlang', outdir: './src/lib/paraglide' }) ], + test: { expect: { requireAssertions: true }, + projects: [ { extends: './vite.config.ts', + test: { name: 'client', + browser: { enabled: true, provider: playwright(), instances: [{ browser: 'chromium', headless: true }] }, + include: ['src/**/*.svelte.{test,spec}.{js,ts}'], exclude: ['src/lib/server/**'] } }, + { extends: './vite.config.ts', + test: { name: 'server', environment: 'node', diff --git a/packages/core/package.json b/packages/core/package.json index df3aaad3c..7019a1447 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -34,7 +34,7 @@ "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", - "esrap": "^1.4.9", + "esrap": "^2.2.1", "htmlparser2": "^9.1.0", "magic-string": "^0.30.21", "picocolors": "^1.1.1", diff --git a/packages/core/tests/js/common/jsdoc-comment/run.ts b/packages/core/tests/js/common/jsdoc-comment/run.ts index c0b6c06f1..9ed0335eb 100644 --- a/packages/core/tests/js/common/jsdoc-comment/run.ts +++ b/packages/core/tests/js/common/jsdoc-comment/run.ts @@ -1,9 +1,14 @@ -import { common, type AstTypes } from '@sveltejs/cli-core/js'; +import { common, type Comments, type AstTypes } from '@sveltejs/cli-core/js'; -export function run(ast: AstTypes.Program): void { +export function run(ast: AstTypes.Program, comments: Comments): void { const functionDeclaration = ast.body[0] as AstTypes.FunctionDeclaration; - common.addJsDocComment(functionDeclaration, { + common.addJsDocComment(functionDeclaration, comments, { + params: { 'import("$lib/paraglide/runtime").AvailableLanguageTag': 'newLanguage' } + }); + + // Adding 2 times the same comment should not add it twice! + common.addJsDocComment(functionDeclaration, comments, { params: { 'import("$lib/paraglide/runtime").AvailableLanguageTag': 'newLanguage' } }); } diff --git a/packages/core/tests/js/common/jsdoc-type-comment/output.ts b/packages/core/tests/js/common/jsdoc-type-comment/output.ts index 1aa734391..e8a715681 100644 --- a/packages/core/tests/js/common/jsdoc-type-comment/output.ts +++ b/packages/core/tests/js/common/jsdoc-type-comment/output.ts @@ -1,2 +1 @@ -/** @type {number} */ -const foo = 42; +/** @type {number} */ const foo = 42; diff --git a/packages/core/tests/js/common/jsdoc-type-comment/run.ts b/packages/core/tests/js/common/jsdoc-type-comment/run.ts index 9b5e5a065..129d725a1 100644 --- a/packages/core/tests/js/common/jsdoc-type-comment/run.ts +++ b/packages/core/tests/js/common/jsdoc-type-comment/run.ts @@ -1,13 +1,13 @@ -import { common, variables, type AstTypes } from '@sveltejs/cli-core/js'; +import { common, variables, type Comments, type AstTypes } from '@sveltejs/cli-core/js'; -export function run(ast: AstTypes.Program): void { +export function run(ast: AstTypes.Program, comments: Comments): void { const declaration = variables.declaration(ast, { kind: 'const', name: 'foo', value: { type: 'Literal', value: 42 } }); - common.addJsDocTypeComment(declaration, { + common.addJsDocTypeComment(declaration, comments, { type: 'number' }); diff --git a/packages/core/tests/js/index.ts b/packages/core/tests/js/index.ts index c7f2021e6..191aaeac7 100644 --- a/packages/core/tests/js/index.ts +++ b/packages/core/tests/js/index.ts @@ -16,13 +16,13 @@ for (const categoryDirectory of categoryDirectories) { const inputFilePath = join(testDirectoryPath, 'input.ts'); const input = fs.existsSync(inputFilePath) ? fs.readFileSync(inputFilePath, 'utf8') : ''; - const ast = parseScript(input); + const { ast, comments } = parseScript(input); // dynamic imports always need to provide the path inline for static analysis const module = await import(`./${categoryDirectory}/${testName}/run.ts`); - module.run(ast); + module.run(ast, comments); - let output = serializeScript(ast, input); + let output = serializeScript(ast, comments, input); if (!output.endsWith('\n')) output += '\n'; await expect(output).toMatchFileSnapshot(`${testDirectoryPath}/output.ts`); }); diff --git a/packages/core/tests/js/object/create/output.ts b/packages/core/tests/js/object/create/output.ts index 28ad96e4f..df190f0f4 100644 --- a/packages/core/tests/js/object/create/output.ts +++ b/packages/core/tests/js/object/create/output.ts @@ -1,15 +1,9 @@ const empty = {}; const created = { foo: 1, bar: 'string' }; -// prettier-ignore const created2 = { foo: 1, bar: 'string', object: { foo: 'hello', nested: { bar: 'world' } }, - array: [ - 123, - 'hello', - { foo: 'bar', bool: true }, - [456, '789'] - ] + array: [123, 'hello', { foo: 'bar', bool: true }, [456, '789']] }; diff --git a/packages/core/tests/js/object/create/run.ts b/packages/core/tests/js/object/create/run.ts index 8bdd272f5..5b19937e0 100644 --- a/packages/core/tests/js/object/create/run.ts +++ b/packages/core/tests/js/object/create/run.ts @@ -37,6 +37,5 @@ export function run(ast: AstTypes.Program): void { name: 'created2', value: createdObject2 }); - createdVariable2.leadingComments = [{ type: 'Line', value: ' prettier-ignore' }]; ast.body.push(createdVariable2); } diff --git a/packages/core/tests/js/object/ensure-nested-property/output.ts b/packages/core/tests/js/object/ensure-nested-property/output.ts index 08bd5fbf9..b3824d36a 100644 --- a/packages/core/tests/js/object/ensure-nested-property/output.ts +++ b/packages/core/tests/js/object/ensure-nested-property/output.ts @@ -1 +1,8 @@ -const test = { a: { /** a comment */ keep: 'you', b: { c: '007' } } }; +const test = { + a: { + /** a comment */ + keep: 'you', + + b: { c: '007' } + } +}; diff --git a/packages/core/tests/js/object/override-property/output.ts b/packages/core/tests/js/object/override-property/output.ts index fbdef76fe..987c50888 100644 --- a/packages/core/tests/js/object/override-property/output.ts +++ b/packages/core/tests/js/object/override-property/output.ts @@ -1 +1,7 @@ -const test = { /** a comment */ foo: 2, bar: 'string2', lorem: false }; +const test = { + /** a comment */ + foo: 2, + + bar: 'string2', + lorem: false +}; diff --git a/packages/core/tests/js/object/property-node/output.ts b/packages/core/tests/js/object/property-node/output.ts index 98da8dea4..3719acb91 100644 --- a/packages/core/tests/js/object/property-node/output.ts +++ b/packages/core/tests/js/object/property-node/output.ts @@ -1 +1,6 @@ -const test = { /*a comment updated*/ foo: 1, /*aka: bond, james bond*/ james: '007' }; +const test = { + /** a comment */ + foo: 1, + + james: '007' +}; diff --git a/packages/core/tests/js/vite/add-plugin-mode/output.ts b/packages/core/tests/js/vite/add-plugin-mode/output.ts index f112d5fcc..df6d4692a 100644 --- a/packages/core/tests/js/vite/add-plugin-mode/output.ts +++ b/packages/core/tests/js/vite/add-plugin-mode/output.ts @@ -7,8 +7,10 @@ import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ firstPlugin(), + // a default plugin sveltekit(), + middlePlugin(), lastPlugin() ] diff --git a/packages/core/tests/js/vite/with-satisfies/output.ts b/packages/core/tests/js/vite/with-satisfies/output.ts index 4d6ed36e0..9c43348e2 100644 --- a/packages/core/tests/js/vite/with-satisfies/output.ts +++ b/packages/core/tests/js/vite/with-satisfies/output.ts @@ -18,23 +18,24 @@ const config = defineConfig({ plugins: [ // all plugins examples, + tailwindcss(), sveltekit(), kitRoutes(), myPlugin() ], + resolve: { alias: { $lib, $routes, $scripts, $actions } }, - build: { - sourcemap: true, - target: 'esnext', - cssMinify: 'lightningcss' - }, + build: { sourcemap: true, target: 'esnext', cssMinify: 'lightningcss' }, + css: { transformer: 'lightningcss', + lightningcss: { targets: browserslistToTargets(browserslist('defaults, not ie 11')) } }, + experimental: { enableNativePlugin: true } }) satisfies UserConfig; diff --git a/packages/core/tests/utils.ts b/packages/core/tests/utils.ts index 10350d1a2..585c6fcaa 100644 --- a/packages/core/tests/utils.ts +++ b/packages/core/tests/utils.ts @@ -4,10 +4,10 @@ import { parseScript, serializeScript, guessIndentString, - guessQuoteStyle, type AstTypes, serializeYaml, - parseYaml + parseYaml, + guessQuoteStyle } from '../tooling/index.ts'; test('guessIndentString - one tab', () => { @@ -54,7 +54,7 @@ test('guessQuoteStyle - single simple', () => { const code = dedent` console.log('asd'); `; - const ast = parseScript(code); + const { ast } = parseScript(code); expect(guessQuoteStyle(ast)).toBe('single'); }); @@ -66,7 +66,7 @@ test('guessQuoteStyle - single complex', () => { console.log("bar"); const foobar = 'foo'; `; - const ast = parseScript(code); + const { ast } = parseScript(code); expect(guessQuoteStyle(ast)).toBe('single'); }); @@ -75,7 +75,7 @@ test('guessQuoteStyle - double simple', () => { const code = dedent` console.log("asd"); `; - const ast = parseScript(code); + const { ast } = parseScript(code); expect(guessQuoteStyle(ast)).toBe('double'); }); @@ -87,7 +87,7 @@ test('guessQuoteStyle - double complex', () => { console.log("bar"); const foobar = "foo"; `; - const ast = parseScript(code); + const { ast } = parseScript(code); expect(guessQuoteStyle(ast)).toBe('double'); }); @@ -96,7 +96,7 @@ test('guessQuoteStyle - no quotes', () => { const code = dedent` const foo = true; `; - const ast = parseScript(code); + const { ast } = parseScript(code); expect(guessQuoteStyle(ast)).toBe(undefined); }); @@ -128,13 +128,13 @@ test('integration - simple', () => { const foobar = "foo"; } `; - const ast = parseScript(code); + const { ast, comments } = parseScript(code); const method = ast.body[1] as AstTypes.FunctionDeclaration; method.body.body.push(newVariableDeclaration); // new variable is added with correct indentation and matching quotes - expect(serializeScript(ast, code)).toMatchInlineSnapshot(` + expect(serializeScript(ast, comments, code)).toMatchInlineSnapshot(` "import foo from 'bar'; function bar() { @@ -155,13 +155,13 @@ test('integration - simple 2', () => { const foobar = 'foo'; } `; - const ast = parseScript(code); + const { ast, comments } = parseScript(code); const method = ast.body[1] as AstTypes.FunctionDeclaration; method.body.body.push(newVariableDeclaration); // new variable is added with correct indentation and matching quotes - expect(serializeScript(ast, code)).toMatchInlineSnapshot(` + expect(serializeScript(ast, comments, code)).toMatchInlineSnapshot(` "import foo from 'bar'; function bar() { @@ -178,14 +178,35 @@ test('integration - preserves comments', () => { /** @type {string} */ let foo = 'bar'; `; - const ast = parseScript(code); + const { ast, comments } = parseScript(code); - expect(serializeScript(ast, code)).toMatchInlineSnapshot(` + expect(serializeScript(ast, comments, code)).toMatchInlineSnapshot(` "/** @type {string} */ let foo = 'bar';" `); }); +test('integration - removes comments', () => { + const code = dedent` + let foo = { + /** @type {string} */ + bar: 'baz', + /** @type {number} */ + baz: 1, + }; + `; + const { ast, comments } = parseScript(code); + comments.remove((c) => c.value.includes('number')); + expect(serializeScript(ast, comments, code)).toMatchInlineSnapshot(` + "let foo = { + /** @type {string} */ + bar: 'baz', + + baz: 1 + };" + `); +}); + describe('yaml', () => { test('read and write', () => { const input = dedent`foo: diff --git a/packages/core/tooling/index.ts b/packages/core/tooling/index.ts index 68f3dc0b4..c9fcc7074 100644 --- a/packages/core/tooling/index.ts +++ b/packages/core/tooling/index.ts @@ -14,9 +14,11 @@ import { } from 'postcss'; import * as fleece from 'silver-fleece'; import { print as esrapPrint } from 'esrap'; +import ts from 'esrap/languages/ts'; import * as acorn from 'acorn'; import { tsPlugin } from '@sveltejs/acorn-typescript'; import * as yaml from 'yaml'; +import type { BaseNode } from 'estree'; export { // html @@ -48,19 +50,21 @@ export type { /** * Parses as string to an AST. Code below is taken from `esrap` to ensure compatibilty. - * https://github.com/sveltejs/esrap/blob/9daf5dd43b31f17f596aa7da91678f2650666dd0/test/common.js#L12 + * https://github.com/sveltejs/esrap/blob/920491535d31484ac5fae2327c7826839d851aed/test/common.js#L14 */ -export function parseScript(content: string): TsEstree.Program { - const comments: TsEstree.Comment[] = []; - +export function parseScript(content: string): { + ast: TsEstree.Program; + comments: Comments; +} { const acornTs = acorn.Parser.extend(tsPlugin()); + const comments = new Comments(); + const internal = transformToInternal(comments); - // Acorn doesn't add comments to the AST by itself. This factory returns the capabilities to add them after the fact. const ast = acornTs.parse(content, { ecmaVersion: 'latest', sourceType: 'module', locations: true, - onComment: (block, value, start, end) => { + onComment: (block, value, start, end, startLoc, endLoc) => { if (block && /\n/.test(value)) { let a = start; while (a > 0 && content[a - 1] !== '\n') a -= 1; @@ -72,39 +76,40 @@ export function parseScript(content: string): TsEstree.Program { value = value.replace(new RegExp(`^${indentation}`, 'gm'), ''); } - comments.push({ type: block ? 'Block' : 'Line', value, start, end }); + internal.original.push({ + type: block ? 'Block' : 'Line', + value, + start, + end, + loc: { start: startLoc as TsEstree.Position, end: endLoc as TsEstree.Position } + }); } }) as TsEstree.Program; - Walker.walk(ast as TsEstree.Node, null, { - _(commentNode, { next }) { - let comment: TsEstree.Comment; - - while (comments[0] && commentNode.start && comments[0].start! < commentNode.start) { - comment = comments.shift()!; - (commentNode.leadingComments ??= []).push(comment); - } - - next(); - - if (comments[0]) { - const slice = content.slice(commentNode.end, comments[0].start); - - if (/^[,) \t]*$/.test(slice)) { - commentNode.trailingComments = [comments.shift()!]; - } - } - } - }); - - return ast; + return { ast, comments }; } -export function serializeScript(ast: TsEstree.Node, previousContent?: string): string { - const { code } = esrapPrint(ast, { - indent: guessIndentString(previousContent), - quotes: guessQuoteStyle(ast) - }); +export function serializeScript( + ast: TsEstree.Node, + comments?: Comments, + previousContent?: string +): string { + const internal = transformToInternal(comments); + const { code } = esrapPrint( + // @ts-expect-error we are still using `estree` while `esrap` is using `@typescript-eslint/types` + // which is causing these errors. But they are similar enough to work together. + ast, + ts({ + // @ts-expect-error see above + comments: internal.original, + getLeadingComments: (node) => internal.leading.get(node), + getTrailingComments: (node) => internal.trailing.get(node), + quotes: guessQuoteStyle(ast) + }), + { + indent: guessIndentString(previousContent) + } + ); return code; } @@ -247,3 +252,42 @@ export function parseYaml(content: string): ReturnType): string { return yaml.stringify(data, { singleQuote: true }); } + +export type CommentType = { type: 'Line' | 'Block'; value: string }; + +export class Comments { + private original: TsEstree.Comment[]; + private leading: WeakMap; + private trailing: WeakMap; + + constructor() { + this.original = []; + this.leading = new WeakMap(); + this.trailing = new WeakMap(); + } + + add(node: BaseNode, comment: CommentType, options?: { position?: 'leading' | 'trailing' }): void { + const { position = 'leading' } = options ?? {}; + const map = position === 'leading' ? this.leading : this.trailing; + const list = map.get(node) ?? []; + // Let's not add 2 times the same comment to one node! + if (!list.find((c) => c.value === comment.value)) { + list.push(comment); + map.set(node, list); + } + } + + remove(predicate: (comment: TsEstree.Comment) => boolean | undefined | null): void { + this.original = this.original.filter((c) => !predicate(c)); + } +} + +interface CommentsInternal { + original: TsEstree.Comment[]; + leading: WeakMap; + trailing: WeakMap; +} + +function transformToInternal(comments: Comments | undefined): CommentsInternal { + return (comments ?? new Comments()) as unknown as CommentsInternal; +} diff --git a/packages/core/tooling/js/common.ts b/packages/core/tooling/js/common.ts index 8812660e2..b1e2c7c8a 100644 --- a/packages/core/tooling/js/common.ts +++ b/packages/core/tooling/js/common.ts @@ -1,18 +1,30 @@ -import { type AstTypes, Walker, parseScript, serializeScript, stripAst } from '../index.ts'; +import { + type AstTypes, + type Comments, + Walker, + parseScript, + serializeScript, + stripAst +} from '../index.ts'; import decircular from 'decircular'; import dedent from 'dedent'; -export function addJsDocTypeComment(node: AstTypes.Node, options: { type: string }): void { +export function addJsDocTypeComment( + node: AstTypes.Node, + comments: Comments, + options: { type: string } +): void { const comment: AstTypes.Comment = { type: 'Block', value: `* @type {${options.type}} ` }; - addComment(node, comment); + comments.add(node, comment); } export function addJsDocComment( node: AstTypes.Node, + comments: Comments, options: { params: Record } ): void { const commentLines: string[] = []; @@ -25,16 +37,7 @@ export function addJsDocComment( value: `*\n * ${commentLines.join('\n * ')}\n ` }; - addComment(node, comment); -} - -function addComment(node: AstTypes.Node, comment: AstTypes.Comment) { - node.leadingComments ??= []; - - const found = node.leadingComments.find( - (item) => item.type === 'Block' && item.value === comment.value - ); - if (!found) node.leadingComments.push(comment); + comments.add(node, comment); } export function typeAnnotate( @@ -118,18 +121,18 @@ export function appendFromString( node: AstTypes.BlockStatement | AstTypes.Program, options: { code: string } ): void { - const program = parseScript(dedent(options.code)); + const { ast } = parseScript(dedent(options.code)); - for (const childNode of program.body) { + for (const childNode of ast.body) { // @ts-expect-error node.body.push(childNode); } } export function parseExpression(code: string): AstTypes.Expression { - const program = parseScript(dedent(code)); - stripAst(program, ['raw']); - const statement = program.body[0]!; + const { ast } = parseScript(dedent(code)); + stripAst(ast, ['raw']); + const statement = ast.body[0]!; if (statement.type !== 'ExpressionStatement') { throw new Error('Code provided was not an expression'); } @@ -142,8 +145,8 @@ export function parseStatement(code: string): AstTypes.Statement { } export function parseFromString(code: string): T { - const program = parseScript(dedent(code)); - const statement = program.body[0]!; + const { ast } = parseScript(dedent(code)); + const statement = ast.body[0]!; return statement as T; } diff --git a/packages/core/tooling/js/index.ts b/packages/core/tooling/js/index.ts index 0b4c6e632..c53336b61 100644 --- a/packages/core/tooling/js/index.ts +++ b/packages/core/tooling/js/index.ts @@ -7,4 +7,4 @@ export * as variables from './variables.ts'; export * as exports from './exports.ts'; export * as kit from './kit.ts'; export * as vite from './vite.ts'; -export type { AstTypes } from '../index.ts'; +export type { AstTypes, Comments } from '../index.ts'; diff --git a/packages/core/tooling/parsers.ts b/packages/core/tooling/parsers.ts index 21b6b0993..94b913fed 100644 --- a/packages/core/tooling/parsers.ts +++ b/packages/core/tooling/parsers.ts @@ -6,11 +6,14 @@ type ParseBase = { generateCode(): string; }; -export function parseScript(source: string): { ast: utils.AstTypes.Program } & ParseBase { - const ast = utils.parseScript(source); - const generateCode = () => utils.serializeScript(ast, source); - - return { ast, source, generateCode }; +export function parseScript(source: string): { + ast: utils.AstTypes.Program; + comments: utils.Comments; +} & ParseBase { + const { ast, comments } = utils.parseScript(source); + const generateCode = () => utils.serializeScript(ast, comments, source); + + return { ast, comments, source, generateCode }; } export function parseCss(source: string): { ast: utils.CssAst } & ParseBase { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2dfbf1915..aaee94169 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,8 +170,8 @@ importers: specifier: ^3.2.2 version: 3.2.2 esrap: - specifier: ^1.4.9 - version: 1.4.9 + specifier: ^2.2.1 + version: 2.2.1 htmlparser2: specifier: ^9.1.0 version: 9.1.0 @@ -1344,12 +1344,12 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - esrap@1.4.9: - resolution: {integrity: sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==} - esrap@2.1.3: resolution: {integrity: sha512-T/Dhhv/QH+yYmiaLz9SA3PW+YyenlnRKDNdtlYJrSOBmNsH4nvPux+mTwx7p+wAedlJrGoZtXNI0a0MjQ2QkVg==} + esrap@2.2.1: + resolution: {integrity: sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -3401,11 +3401,11 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@1.4.9: + esrap@2.1.3: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - esrap@2.1.3: + esrap@2.2.1: dependencies: '@jridgewell/sourcemap-codec': 1.5.5