diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index 373b36483..2af1ccd48 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -1,5 +1,5 @@ import { RawSourceMap, SourceMapConsumer } from 'source-map'; -import svelte2tsx from 'svelte2tsx'; +import svelte2tsx, { IExportedNames } from 'svelte2tsx'; import ts from 'typescript'; import { Position, Range } from 'vscode-languageserver'; import { @@ -82,10 +82,14 @@ export namespace DocumentSnapshot { * @param options options that apply to the svelte document */ export function fromDocument(document: Document, options: SvelteSnapshotOptions) { - const { tsxMap, text, parserError, nrPrependedLines, scriptKind } = preprocessSvelteFile( - document, - options, - ); + const { + tsxMap, + text, + exportedNames, + parserError, + nrPrependedLines, + scriptKind, + } = preprocessSvelteFile(document, options); return new SvelteDocumentSnapshot( document, @@ -93,6 +97,7 @@ export namespace DocumentSnapshot { scriptKind, text, nrPrependedLines, + exportedNames, tsxMap, ); } @@ -121,6 +126,7 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions let parserError: ParserError | null = null; let nrPrependedLines = 0; let text = document.getText(); + let exportedNames: IExportedNames = { has: () => false }; const scriptKind = [ getScriptKindFromAttributes(document.scriptInfo?.attributes ?? {}), @@ -137,6 +143,7 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions }); text = tsx.code; tsxMap = tsx.map; + exportedNames = tsx.exportedNames; if (tsxMap) { tsxMap.sources = [document.uri]; @@ -164,7 +171,7 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions text = document.scriptInfo ? document.scriptInfo.content : ''; } - return { tsxMap, text, parserError, nrPrependedLines, scriptKind }; + return { tsxMap, text, exportedNames, parserError, nrPrependedLines, scriptKind }; } /** @@ -181,6 +188,7 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot { public readonly scriptKind: ts.ScriptKind, private readonly text: string, private readonly nrPrependedLines: number, + private readonly exportedNames: IExportedNames, private readonly tsxMap?: RawSourceMap, ) {} @@ -204,6 +212,10 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot { return positionAt(offset, this.text); } + hasProp(name: string): boolean { + return this.exportedNames.has(name); + } + async getFragment() { if (!this.fragment) { const uri = pathToUrl(this.filePath); diff --git a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts index 1e21b2577..47b059614 100644 --- a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts @@ -4,6 +4,7 @@ import { Document, mapDiagnosticToOriginal, getTextInRange } from '../../../lib/ import { DiagnosticsProvider } from '../../interfaces'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; import { convertRange, mapSeverity } from '../utils'; +import { SvelteDocumentSnapshot } from '../DocumentSnapshot'; export class DiagnosticsProviderImpl implements DiagnosticsProvider { constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {} @@ -43,7 +44,7 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider { })) .map((diagnostic) => mapDiagnosticToOriginal(fragment, diagnostic)) .filter(hasNoNegativeLines) - .filter(isNoFalsePositive(document.getText())); + .filter(isNoFalsePositive(document.getText(), tsDoc)); } private getLSAndTSDoc(document: Document) { @@ -60,12 +61,12 @@ function hasNoNegativeLines(diagnostic: Diagnostic): boolean { return diagnostic.range.start.line >= 0 && diagnostic.range.end.line >= 0; } -function isNoFalsePositive(text: string) { +function isNoFalsePositive(text: string, tsDoc: SvelteDocumentSnapshot) { return (diagnostic: Diagnostic) => { return ( isNoJsxCannotHaveMultipleAttrsError(diagnostic) && isNoUnusedLabelWarningForReactiveStatement(diagnostic) && - isNoUsedBeforeAssigned(diagnostic, text) + isNoUsedBeforeAssigned(diagnostic, text, tsDoc) ); }; } @@ -75,13 +76,16 @@ function isNoFalsePositive(text: string) { * without assigning a value in strict mode. Should not throw an error here * but on the component-user-side ("you did not set a required prop"). */ -function isNoUsedBeforeAssigned(diagnostic: Diagnostic, text: string): boolean { +function isNoUsedBeforeAssigned( + diagnostic: Diagnostic, + text: string, + tsDoc: SvelteDocumentSnapshot, +): boolean { if (diagnostic.code !== 2454) { return true; } - const exportLetRegex = new RegExp(`export\\s+let\\s+${getTextInRange(diagnostic.range, text)}`); - return !exportLetRegex.test(text); + return !tsDoc.hasProp(getTextInRange(diagnostic.range, text)); } /** diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-falsepositives.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics-falsepositives.svelte index 474ff07eb..e4e711152 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-falsepositives.svelte +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics-falsepositives.svelte @@ -1,8 +1,9 @@

{bla} -{noUsedBeforeDeclare} \ No newline at end of file +{noUsedBeforeDeclare} +{anotherUsed} \ No newline at end of file diff --git a/packages/svelte2tsx/index.d.ts b/packages/svelte2tsx/index.d.ts index eef92729a..1cdcb43bb 100644 --- a/packages/svelte2tsx/index.d.ts +++ b/packages/svelte2tsx/index.d.ts @@ -1,6 +1,11 @@ -type SvelteCompiledToTsx = { - code: string, - map: import("magic-string").SourceMap +export interface SvelteCompiledToTsx { + code: string; + map: import("magic-string").SourceMap; + exportedNames: IExportedNames; +} + +export interface IExportedNames { + has(name: string): boolean; } export default function svelte2tsx( diff --git a/packages/svelte2tsx/src/nodes/ExportedNames.ts b/packages/svelte2tsx/src/nodes/ExportedNames.ts index 30f59a842..dfd9b977a 100644 --- a/packages/svelte2tsx/src/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/nodes/ExportedNames.ts @@ -1,11 +1,18 @@ -export class ExportedNames extends Map< - string, - { - type?: string; - identifierText?: string; - required?: boolean; - } -> { +// eslint-disable-next-line @typescript-eslint/interface-name-prefix +export interface IExportedNames { + has(name: string): boolean; +} + +export class ExportedNames + extends Map< + string, + { + type?: string; + identifierText?: string; + required?: boolean; + } + > + implements IExportedNames { /** * Creates a string from the collected props * diff --git a/packages/svelte2tsx/src/svelte2tsx.ts b/packages/svelte2tsx/src/svelte2tsx.ts index 9688a1a8f..7a1df0992 100644 --- a/packages/svelte2tsx/src/svelte2tsx.ts +++ b/packages/svelte2tsx/src/svelte2tsx.ts @@ -1032,5 +1032,6 @@ export function svelte2tsx( return { code: str.toString(), map: str.generateMap({ hires: true, source: options?.filename }), + exportedNames }; }