diff --git a/packages/core/src/transform/diagnostics/augmentation.ts b/packages/core/src/transform/diagnostics/augmentation.ts index 37c9103ef..55ba9bac5 100644 --- a/packages/core/src/transform/diagnostics/augmentation.ts +++ b/packages/core/src/transform/diagnostics/augmentation.ts @@ -2,7 +2,10 @@ import type ts from 'typescript'; import { Diagnostic } from './index.js'; import GlimmerASTMappingTree, { MappingSource } from '../template/glimmer-ast-mapping-tree.js'; import TransformedModule from '../template/transformed-module.js'; + export function augmentDiagnostics( + ts: typeof import('typescript'), + sourceFile: ts.SourceFile, transformedModule: TransformedModule, diagnostics: T[], ): T[] { @@ -26,7 +29,11 @@ export function augmentDiagnostics( return rangeWithMappingAndSource.mapping || null; }; - const augmentedDiagnostics: T[] = []; + const unusedExpectErrors = new Set( + transformedModule.directives.filter((d) => d.kind === 'expect-error'), + ); + + const augmentedDiagnostics: Diagnostic[] = []; for (const diagnostic of diagnostics) { const augmentedDiagnostic = rewriteMessageText(diagnostic, mappingForDiagnostic); @@ -41,15 +48,29 @@ export function augmentDiagnostics( ); if (appliedDirective) { + if (appliedDirective.kind === 'expect-error') { + unusedExpectErrors.delete(appliedDirective); + } // Filter out this diagnostic; its covered by a directive. continue; } } - // @ts-expect-error not sure how to fix augmentedDiagnostics.push(augmentedDiagnostic); } + for (const unusedExpectError of unusedExpectErrors) { + augmentedDiagnostics.push({ + category: ts.DiagnosticCategory.Error, + code: 2578, + file: sourceFile, + start: unusedExpectError.location.start, + length: unusedExpectError.location.end - unusedExpectError.location.start, + messageText: `Unused '@glint-expect-error' directive.`, + }); + } + + // @ts-expect-error not sure how to fix return augmentedDiagnostics; } diff --git a/packages/tsserver-plugin/src/typescript-server-plugin.ts b/packages/tsserver-plugin/src/typescript-server-plugin.ts index e51c34d2a..520f1b967 100644 --- a/packages/tsserver-plugin/src/typescript-server-plugin.ts +++ b/packages/tsserver-plugin/src/typescript-server-plugin.ts @@ -202,7 +202,12 @@ function getSemanticDiagnostics( return tsDiagnostics; } - const augmentedTsDiagnostics = augmentDiagnostics(transformedModule, tsDiagnostics); + const augmentedTsDiagnostics = augmentDiagnostics( + ts, + sourceFile, + transformedModule, + tsDiagnostics, + ); return augmentedTsDiagnostics; }; diff --git a/test-packages/package-test-core/__tests__/language-server/diagnostics.test.ts b/test-packages/package-test-core/__tests__/language-server/diagnostics.test.ts index f432d13f6..ce6ad8150 100644 --- a/test-packages/package-test-core/__tests__/language-server/diagnostics.test.ts +++ b/test-packages/package-test-core/__tests__/language-server/diagnostics.test.ts @@ -637,6 +637,43 @@ describe('Language Server: Diagnostics (ts plugin)', () => { `); }); + test('unused @glint-expect-error triggers a diagnostic', async () => { + const componentA = stripIndent` + import Component from '@glimmer/component'; + + export default class ComponentA extends Component { + + } + `; + + const diagnostics = await requestDiagnostics( + 'ts-template-imports-app/src/ephemeral-index.gts', + 'glimmer-ts', + componentA, + ); + + expect(diagnostics).toMatchInlineSnapshot(` + [ + { + "category": "error", + "code": 2578, + "end": { + "line": 5, + "offset": 31, + }, + "start": { + "line": 5, + "offset": 5, + }, + "text": "Unused '@glint-expect-error' directive.", + }, + ] + `); + }); + test('svg does not produce false positives', async () => { const code = stripIndent` import Component from '@glimmer/component';