From b12afd18df0cb5544bebeb57a8aa615acbd6819f Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Wed, 9 Oct 2024 20:29:48 +0200 Subject: [PATCH 1/3] fix: include named exports in svelte 5 type (#2528) fixes sveltejs/svelte#13508 --- packages/svelte2tsx/repl/index.svelte | 10 ++++---- packages/svelte2tsx/src/svelte2tsx/index.ts | 23 ++++++++++++++--- .../src/svelte2tsx/nodes/ExportedNames.ts | 24 +++++++++++------- .../processInstanceScriptContent.ts | 12 +++++++-- .../export-list-runes.v5/expectedv2.ts | 25 +++++++++++++++++++ .../samples/export-list-runes.v5/input.svelte | 19 ++++++++++++++ .../renamed-exports-runes.v5/expectedv2.ts | 13 ++++++++++ .../renamed-exports-runes.v5/input.svelte | 7 ++++++ .../ts-export-list-runes.v5/expectedv2.ts | 25 +++++++++++++++++++ .../ts-export-list-runes.v5/input.svelte | 19 ++++++++++++++ 10 files changed, 157 insertions(+), 20 deletions(-) create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/export-list-runes.v5/expectedv2.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/export-list-runes.v5/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports-runes.v5/expectedv2.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports-runes.v5/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/input.svelte diff --git a/packages/svelte2tsx/repl/index.svelte b/packages/svelte2tsx/repl/index.svelte index 49517cc72..4b1efc883 100644 --- a/packages/svelte2tsx/repl/index.svelte +++ b/packages/svelte2tsx/repl/index.svelte @@ -1,7 +1,7 @@ + + - -{#if value} - -{/if} diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index 91e0d6b5a..87e6779c4 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -44,6 +44,7 @@ type TemplateProcessResult = { events: ComponentEvents; resolvedStores: string[]; usesAccessors: boolean; + isRunes: boolean; }; function processSvelteTemplate( @@ -64,6 +65,7 @@ function processSvelteTemplate( let uses$$restProps = false; let uses$$slots = false; let usesAccessors = !!options.accessors; + let isRunes = false; const componentDocumentation = new ComponentDocumentation(); @@ -92,6 +94,9 @@ function processSvelteTemplate( usesAccessors = true; } break; + case 'runes': + isRunes = true; + break; } } }; @@ -303,7 +308,8 @@ function processSvelteTemplate( uses$$slots, componentDocumentation, resolvedStores, - usesAccessors + usesAccessors, + isRunes }; } @@ -342,7 +348,8 @@ export function svelte2tsx( events, componentDocumentation, resolvedStores, - usesAccessors + usesAccessors, + isRunes } = processSvelteTemplate(str, options.parse || parse, { ...options, svelte5Plus @@ -370,7 +377,14 @@ export function svelte2tsx( : instanceScriptTarget; const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart); //move the instance script and process the content - let exportedNames = new ExportedNames(str, 0, basename, options?.isTsFile, svelte5Plus); + let exportedNames = new ExportedNames( + str, + 0, + basename, + options?.isTsFile, + svelte5Plus, + isRunes + ); let generics = new Generics(str, 0, { attributes: [] } as any); let uses$$SlotsInterface = false; if (scriptTag) { @@ -387,7 +401,8 @@ export function svelte2tsx( /**hasModuleScripts */ !!moduleScriptTag, options?.isTsFile, basename, - svelte5Plus + svelte5Plus, + isRunes ); uses$$props = uses$$props || res.uses$$props; uses$$restProps = uses$$restProps || res.uses$$restProps; diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts index 3f5ec412a..8184da952 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts @@ -17,6 +17,7 @@ interface ExportedName { identifierText?: string; required?: boolean; doc?: string; + isNamedExport?: boolean; } export class ExportedNames { @@ -54,7 +55,8 @@ export class ExportedNames { private astOffset: number, private basename: string, private isTsFile: boolean, - private isSvelte5Plus: boolean + private isSvelte5Plus: boolean, + private isRunes: boolean ) {} handleVariableStatement(node: ts.VariableStatement, parent: ts.Node): void { @@ -127,9 +129,9 @@ export class ExportedNames { if (ts.isNamedExports(exportClause)) { for (const ne of exportClause.elements) { if (ne.propertyName) { - this.addExport(ne.propertyName, false, ne.name); + this.addExport(ne.propertyName, false, ne.name, undefined, undefined, true); } else { - this.addExport(ne.name, false); + this.addExport(ne.name, false, undefined, undefined, undefined, true); } } //we can remove entire statement @@ -552,24 +554,26 @@ export class ExportedNames { isLet: boolean, target: ts.Identifier = null, type: ts.TypeNode = null, - required = false + required = false, + isNamedExport = false ): void { const existingDeclaration = this.possibleExports.get(name.text); - if (target) { this.exports.set(name.text, { isLet: isLet || existingDeclaration?.isLet, type: type?.getText() || existingDeclaration?.type, identifierText: target.text, required: required || existingDeclaration?.required, - doc: this.getDoc(target) || existingDeclaration?.doc + doc: this.getDoc(target) || existingDeclaration?.doc, + isNamedExport }); } else { this.exports.set(name.text, { isLet: isLet || existingDeclaration?.isLet, type: existingDeclaration?.type, required: existingDeclaration?.required, - doc: existingDeclaration?.doc + doc: existingDeclaration?.doc, + isNamedExport }); } @@ -706,7 +710,9 @@ export class ExportedNames { */ createExportsStr(): string { const names = Array.from(this.exports.entries()); - const others = names.filter(([, { isLet }]) => !isLet); + const others = names.filter( + ([, { isLet, isNamedExport }]) => !isLet || (this.usesRunes() && isNamedExport) + ); const needsAccessors = this.usesAccessors && names.length > 0 && !this.usesRunes(); // runes mode doesn't support accessors if (this.isSvelte5Plus) { @@ -803,6 +809,6 @@ export class ExportedNames { } usesRunes() { - return this.hasRunesGlobals || this.hasPropsRune(); + return this.hasRunesGlobals || this.hasPropsRune() || this.isRunes; } } diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index e226c2b1b..044aeb83a 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -42,7 +42,8 @@ export function processInstanceScriptContent( hasModuleScript: boolean, isTSFile: boolean, basename: string, - isSvelte5Plus: boolean + isSvelte5Plus: boolean, + isRunes: boolean ): InstanceScriptProcessResult { const htmlx = str.original; const scriptContent = htmlx.substring(script.content.start, script.content.end); @@ -54,7 +55,14 @@ export function processInstanceScriptContent( ts.ScriptKind.TS ); const astOffset = script.content.start; - const exportedNames = new ExportedNames(str, astOffset, basename, isTSFile, isSvelte5Plus); + const exportedNames = new ExportedNames( + str, + astOffset, + basename, + isTSFile, + isSvelte5Plus, + isRunes + ); const generics = new Generics(str, astOffset, script); const interfacesAndTypes = new InterfacesAndTypes(); diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-list-runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/export-list-runes.v5/expectedv2.ts new file mode 100644 index 000000000..e99e80294 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-list-runes.v5/expectedv2.ts @@ -0,0 +1,25 @@ +/// +;function render() { + + let name1 = "world" + let name2/*Ωignore_startΩ*/;name2 = __sveltets_2_any(name2);/*Ωignore_endΩ*/ + + let rename1 = ''; + let rename2/*Ωignore_startΩ*/;rename2 = __sveltets_2_any(rename2);/*Ωignore_endΩ*/; + + class Foo {} + function bar() {} + const baz = ''; + + class RenameFoo {} + function renamebar() {} + const renamebaz = ''; + + +; +async () => { { svelteHTML.createElement("svelte:options", {"runes":true,});} + +}; +return { props: /** @type {Record} */ ({}), exports: /** @type {{name1: typeof name1,name2: typeof name2,renamed1: typeof rename1,renamed2: typeof rename2,Foo: typeof Foo,bar: typeof bar,baz: typeof baz,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: typeof renamebaz}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-list-runes.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/export-list-runes.v5/input.svelte new file mode 100644 index 000000000..999d14122 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-list-runes.v5/input.svelte @@ -0,0 +1,19 @@ + + + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports-runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports-runes.v5/expectedv2.ts new file mode 100644 index 000000000..8461d6616 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports-runes.v5/expectedv2.ts @@ -0,0 +1,13 @@ +/// +;function render() { + + let name = "world" + let name2 = "world" + +; +async () => { { svelteHTML.createElement("svelte:options", {"runes":true,});} + +}; +return { props: /** @type {Record} */ ({}), exports: /** @type {{name3: typeof name,name4: typeof name2}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports-runes.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports-runes.v5/input.svelte new file mode 100644 index 000000000..4b1efc883 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports-runes.v5/input.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts new file mode 100644 index 000000000..45c93ecf4 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts @@ -0,0 +1,25 @@ +/// +;function render() { + + let name1: string = "world"/*Ωignore_startΩ*/;name1 = __sveltets_2_any(name1);/*Ωignore_endΩ*/ + let name2: string/*Ωignore_startΩ*/;name2 = __sveltets_2_any(name2);/*Ωignore_endΩ*/; + let name3: string = ''/*Ωignore_startΩ*/;name3 = __sveltets_2_any(name3);/*Ωignore_endΩ*/;let name4: string/*Ωignore_startΩ*/;name4 = __sveltets_2_any(name4);/*Ωignore_endΩ*/; + + let rename1: string = ''/*Ωignore_startΩ*/;rename1 = __sveltets_2_any(rename1);/*Ωignore_endΩ*/; + let rename2: string/*Ωignore_startΩ*/;rename2 = __sveltets_2_any(rename2);/*Ωignore_endΩ*/; + + class Foo {} + function bar() {} + const baz: string = ''; + + class RenameFoo {} + function renamebar() {} + const renamebaz: string = ''; + + +; +async () => { { svelteHTML.createElement("svelte:options", {"runes":true,});} +}; +return { props: {} as Record, exports: {} as any as { name1: string,name2: string,name3: string,name4: string,renamed1: string,renamed2: string,Foo: typeof Foo,bar: typeof bar,baz: string,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: string }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/input.svelte new file mode 100644 index 000000000..11ceadd74 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/input.svelte @@ -0,0 +1,19 @@ + + From a167a93aaee1ecb5694a6f2ac869409c8b535f35 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 10 Oct 2024 13:46:25 +0200 Subject: [PATCH 2/3] feat: provide Svelte 5 component migration command --- .../src/plugins/svelte/SveltePlugin.ts | 36 ++++++++++++++++++- packages/language-server/src/server.ts | 17 +++++---- packages/svelte-vscode/package.json | 4 +++ packages/svelte-vscode/src/extension.ts | 18 ++++++++++ 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/packages/language-server/src/plugins/svelte/SveltePlugin.ts b/packages/language-server/src/plugins/svelte/SveltePlugin.ts index 3aeb39cf5..e9a39f4f5 100644 --- a/packages/language-server/src/plugins/svelte/SveltePlugin.ts +++ b/packages/language-server/src/plugins/svelte/SveltePlugin.ts @@ -15,7 +15,7 @@ import { WorkspaceEdit } from 'vscode-languageserver'; import { Plugin } from 'prettier'; -import { getPackageInfo, importPrettier } from '../../importPackage'; +import { getPackageInfo, importPrettier, importSvelte } from '../../importPackage'; import { Document } from '../../lib/documents'; import { Logger } from '../../logger'; import { LSConfigManager, LSSvelteConfig } from '../../ls-config'; @@ -288,6 +288,10 @@ export class SveltePlugin command: string, args?: any[] ): Promise { + if (command === 'migrate_to_svelte_5') { + return this.migrate(document); + } + if (!this.featureEnabled('codeActions')) { return null; } @@ -300,6 +304,36 @@ export class SveltePlugin } } + private migrate(document: Document): WorkspaceEdit | string { + try { + const compiler = importSvelte(document.getFilePath() ?? '') as any; + if (!compiler.migrate) { + return 'Your installed Svelte version does not support migration'; + } + + const migrated = compiler.migrate(document.getText(), { + filename: document.getFilePath() ?? undefined + }); + + return { + changes: { + [document.uri]: [ + TextEdit.replace( + Range.create( + document.positionAt(0), + document.positionAt(document.getTextLength()) + ), + migrated.code + ) + ] + } + }; + } catch (error: any) { + Logger.error('Failed to migrate Svelte file', error); + return error?.message ?? 'Failed to migrate Svelte file'; + } + } + async getSelectionRange( document: Document, position: Position diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index d19e56dcc..026edff5d 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -290,6 +290,7 @@ export function startServer(options?: LSOptions) { 'constant_scope_2', 'constant_scope_3', 'extract_to_svelte_component', + 'migrate_to_svelte_5', 'Infer function return type' ] } @@ -592,15 +593,13 @@ export function startServer(options?: LSOptions) { return null; } - if (doc) { - const compiled = await sveltePlugin.getCompiledResult(doc); - if (compiled) { - const js = compiled.js; - const css = compiled.css; - return { js, css }; - } else { - return null; - } + const compiled = await sveltePlugin.getCompiledResult(doc); + if (compiled) { + const js = compiled.js; + const css = compiled.css; + return { js, css }; + } else { + return null; } }); diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index a39165e17..12260c440 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -540,6 +540,10 @@ "command": "svelte.extractComponent", "title": "Svelte: Extract Component" }, + { + "command": "svelte.migrate_to_svelte_5", + "title": "Svelte: Migrate Component to Svelte 5 Syntax" + }, { "command": "svelte.typescript.findAllFileReferences", "title": "Svelte: Find File References" diff --git a/packages/svelte-vscode/src/extension.ts b/packages/svelte-vscode/src/extension.ts index d8ae0e166..c159d0a98 100644 --- a/packages/svelte-vscode/src/extension.ts +++ b/packages/svelte-vscode/src/extension.ts @@ -259,6 +259,8 @@ export function activateSvelteLanguageServer(context: ExtensionContext) { addExtracComponentCommand(getLS, context); + addMigrateToSvelte5Command(getLS, context); + languages.setLanguageConfiguration('svelte', { indentationRules: { // Matches a valid opening tag that is: @@ -495,6 +497,22 @@ function addExtracComponentCommand(getLS: () => LanguageClient, context: Extensi ); } +function addMigrateToSvelte5Command(getLS: () => LanguageClient, context: ExtensionContext) { + context.subscriptions.push( + commands.registerTextEditorCommand('svelte.migrate_to_svelte_5', async (editor) => { + if (editor?.document?.languageId !== 'svelte') { + return; + } + + const uri = editor.document.uri.toString(); + getLS().sendRequest(ExecuteCommandRequest.type, { + command: 'migrate_to_svelte_5', + arguments: [uri] + }); + }) + ); +} + function createLanguageServer(serverOptions: ServerOptions, clientOptions: LanguageClientOptions) { return new LanguageClient('svelte', 'Svelte', serverOptions, clientOptions); } From 156bd7d7f445899dd7cf861495a570059d9e47ef Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Thu, 10 Oct 2024 21:39:28 +0200 Subject: [PATCH 3/3] fix: correctly position quick fixes in instance and module (#2531) closes #2529 --- .../features/getCodeActions/getQuickfixes.ts | 63 +++++++++++++------ .../svelte/features/getCodeAction.test.ts | 49 +++++++++++++++ .../svelte-ignore-code-action.svelte | 6 ++ 3 files changed, 98 insertions(+), 20 deletions(-) diff --git a/packages/language-server/src/plugins/svelte/features/getCodeActions/getQuickfixes.ts b/packages/language-server/src/plugins/svelte/features/getCodeActions/getQuickfixes.ts index 917325c88..037ea439d 100644 --- a/packages/language-server/src/plugins/svelte/features/getCodeActions/getQuickfixes.ts +++ b/packages/language-server/src/plugins/svelte/features/getCodeActions/getQuickfixes.ts @@ -1,4 +1,4 @@ -import { walk } from 'estree-walker'; +import { BaseNode, walk } from 'estree-walker'; import { EOL } from 'os'; // @ts-ignore import { TemplateNode } from 'svelte/types/compiler/interfaces'; @@ -25,6 +25,8 @@ import ts from 'typescript'; // but the AST returned by svelte/compiler does. Type as any as a workaround. type Node = any; +type Ast = Awaited>['ast']; + /** * Get applicable quick fixes. */ @@ -41,7 +43,6 @@ export async function getQuickfixActions( const transpiled = await svelteDoc.getTranspiled(); const content = transpiled.getText(); const lineOffsets = getLineOffsets(content); - const { html } = ast; const codeActions: CodeAction[] = []; @@ -52,7 +53,7 @@ export async function getQuickfixActions( transpiled, content, lineOffsets, - html, + ast, diagnostic )) ); @@ -66,7 +67,7 @@ async function createQuickfixActions( transpiled: ITranspiledSvelteDocument, content: string, lineOffsets: number[], - html: TemplateNode, + ast: Ast, diagnostic: Diagnostic ): Promise { const { @@ -80,7 +81,21 @@ async function createQuickfixActions( pos: diagnosticStartOffset, end: diagnosticEndOffset }; - const node = findTagForRange(html, offsetRange); + const { html, instance, module } = ast; + const tree = [html, instance, module].find((part) => { + return ( + part?.start != null && + offsetRange.pos >= part.start && + part?.end != null && + offsetRange.pos <= part.end && + part?.end != null && + offsetRange.end <= part.end && + part?.start != null && + offsetRange.end >= part.start + ); + }); + + const node = findTagForRange(tree!, offsetRange, tree === html); const codeActions: CodeAction[] = []; @@ -103,7 +118,8 @@ async function createQuickfixActions( content, lineOffsets, node, - diagnostic + diagnostic, + tree === html ) ); @@ -146,14 +162,15 @@ function createSvelteIgnoreQuickfixAction( content: string, lineOffsets: number[], node: Node, - diagnostic: Diagnostic + diagnostic: Diagnostic, + isHtml: boolean ): CodeAction { return CodeAction.create( getCodeActionTitle(diagnostic), { documentChanges: [ TextDocumentEdit.create(textDocument, [ - getSvelteIgnoreEdit(transpiled, content, lineOffsets, node, diagnostic) + getSvelteIgnoreEdit(transpiled, content, lineOffsets, node, diagnostic, isHtml) ]) ] }, @@ -190,7 +207,8 @@ function getSvelteIgnoreEdit( content: string, lineOffsets: number[], node: Node, - diagnostic: Diagnostic + diagnostic: Diagnostic, + isHtml: boolean ) { const { code } = diagnostic; @@ -207,7 +225,10 @@ function getSvelteIgnoreEdit( const indent = getIndent(afterStartLineStart); // TODO: Make all code action's new line consistent - const ignore = `${indent}${EOL}`; + let ignore = `${indent}// svelte-ignore ${code}${EOL}${indent}`; + if (isHtml) { + ignore = `${indent}${EOL}`; + } const position = Position.create(nodeStartPosition.line, 0); return mapObjWithRangeToOriginal(transpiled, TextEdit.insert(position, ignore)); @@ -215,18 +236,20 @@ function getSvelteIgnoreEdit( const elementOrComponent = ['Component', 'Element', 'InlineComponent']; -function findTagForRange(html: Node, range: ts.TextRange) { - let nearest = html; +function findTagForRange(ast: BaseNode, range: ts.TextRange, isHtml: boolean) { + let nearest: BaseNode = ast; - walk(html, { + walk(ast, { enter(node, parent) { - const { type } = node; - const isBlock = 'block' in node || node.type.toLowerCase().includes('block'); - const isFragment = type === 'Fragment'; - const keepLooking = isFragment || elementOrComponent.includes(type) || isBlock; - if (!keepLooking) { - this.skip(); - return; + if (isHtml) { + const { type } = node; + const isBlock = 'block' in node || node.type.toLowerCase().includes('block'); + const isFragment = type === 'Fragment'; + const keepLooking = isFragment || elementOrComponent.includes(type) || isBlock; + if (!keepLooking) { + this.skip(); + return; + } } if (within(node, range) && parent === nearest) { diff --git a/packages/language-server/test/plugins/svelte/features/getCodeAction.test.ts b/packages/language-server/test/plugins/svelte/features/getCodeAction.test.ts index e94dcd2fa..617ad0b4f 100644 --- a/packages/language-server/test/plugins/svelte/features/getCodeAction.test.ts +++ b/packages/language-server/test/plugins/svelte/features/getCodeAction.test.ts @@ -421,6 +421,55 @@ describe('SveltePlugin#getCodeAction', () => { } ]); }); + + it('should provide ignore comment in script tags', async () => { + ( + await expectCodeActionFor(svelteIgnoreCodeAction, { + diagnostics: [ + { + severity: DiagnosticSeverity.Warning, + code: 'state_referenced_locally', + range: Range.create( + { line: 13, character: 9 }, + { line: 13, character: 14 } + ), + message: '', + source: 'svelte' + } + ] + }) + ).toEqual([ + { + edit: { + documentChanges: [ + { + edits: [ + { + newText: `\t// svelte-ignore state_referenced_locally${EOL}\t`, + range: { + end: { + character: 0, + line: 13 + }, + start: { + character: 0, + line: 13 + } + } + } + ], + textDocument: { + uri: getUri(svelteIgnoreCodeAction), + version: null + } + } + ] + }, + title: '(svelte) Disable state_referenced_locally for this line', + kind: 'quickfix' + } + ]); + }); }); describe('It should provide svelte ignore code actions (TypeScript)', () => { diff --git a/packages/language-server/test/plugins/svelte/testfiles/svelte-ignore-code-action.svelte b/packages/language-server/test/plugins/svelte/testfiles/svelte-ignore-code-action.svelte index 14bb3276e..7d01939d7 100644 --- a/packages/language-server/test/plugins/svelte/testfiles/svelte-ignore-code-action.svelte +++ b/packages/language-server/test/plugins/svelte/testfiles/svelte-ignore-code-action.svelte @@ -7,3 +7,9 @@ href="" >about {/if} + + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy