diff --git a/src/traceDiagnosticFilter.ts b/src/traceDiagnosticFilter.ts new file mode 100644 index 0000000..ed4cb29 --- /dev/null +++ b/src/traceDiagnosticFilter.ts @@ -0,0 +1,32 @@ +import * as ts from 'typescript' + +const declarationFilePattern = /\.d\.(?:cts|mts|ts)$/i + +export function shouldSuppressTraceDiagnostic(fileName: string, sourceText: string, position: number): boolean { + return createTraceDiagnosticFilter(fileName, sourceText)(position) +} + +export function createTraceDiagnosticFilter(fileName: string, sourceText: string) { + if (declarationFilePattern.test(fileName)) + return () => true + + const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true) + return (position: number) => { + const node = findNodeAtPosition(sourceFile, position) + + for (let current = node; current; current = current.parent) { + if (ts.isTypeAliasDeclaration(current) || ts.isInterfaceDeclaration(current)) + return true + } + + return false + } +} + +function findNodeAtPosition(node: ts.Node, position: number): ts.Node | undefined { + if (position < node.getStart() || position >= node.getEnd()) + return undefined + + const child = ts.forEachChild(node, current => findNodeAtPosition(current, position)) + return child ?? node +} diff --git a/src/traceDiagnostics.ts b/src/traceDiagnostics.ts index 48e5601..7af59d9 100644 --- a/src/traceDiagnostics.ts +++ b/src/traceDiagnostics.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode' import type { FileStat } from '../shared/src/messages' import { getStatsFromTree } from './traceTree' import { afterConfigUpdate, getCurrentConfig } from './configuration' +import { createTraceDiagnosticFilter } from './traceDiagnosticFilter' let diagnosticCollection: vscode.DiagnosticCollection @@ -49,6 +50,8 @@ export async function addTraceDiagnostics(fileName: string, stats: FileStat[]) { fileStatus.set(fileName, 'clean') const document = await vscode.workspace.openTextDocument(uri) + const sourceText = document.getText() + const shouldSuppressTraceDiagnostic = createTraceDiagnosticFilter(document.fileName, sourceText) let averages @@ -72,6 +75,9 @@ export async function addTraceDiagnostics(fileName: string, stats: FileStat[]) { if (lastPos === stat.pos) continue // do not create diagnostics for further checks at the same starting position + if (shouldSuppressTraceDiagnostic(stat.pos)) + continue + const diagnostic = toDiagnistic(stat, document, averages!) if (diagnostic) diagnostics.push(diagnostic) diff --git a/test/trace-diagnostic-filter.test.ts b/test/trace-diagnostic-filter.test.ts new file mode 100644 index 0000000..d81def7 --- /dev/null +++ b/test/trace-diagnostic-filter.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest' +import { shouldSuppressTraceDiagnostic } from '../src/traceDiagnosticFilter' + +describe('shouldSuppressTraceDiagnostic', () => { + it('suppresses declaration files', () => { + expect(shouldSuppressTraceDiagnostic('node_modules/typescript/lib/lib.es2020.date.d.ts', 'declare const Date: DateConstructor', 10)).toBe(true) + }) + + it('suppresses type alias diagnostics anywhere in the declaration', () => { + const source = `export type ConstructorOf = new ( + ...args: any[] +) => Instance` + + expect(shouldSuppressTraceDiagnostic('src/example.ts', source, source.indexOf('any[]'))).toBe(true) + }) + + it('suppresses interface diagnostics', () => { + const source = `export interface Person { + name: string +}` + + expect(shouldSuppressTraceDiagnostic('src/example.ts', source, source.indexOf('string'))).toBe(true) + }) + + it('keeps value-level diagnostics', () => { + const source = `export const person: Person = { + name: 'Ada', +}` + + expect(shouldSuppressTraceDiagnostic('src/example.ts', source, source.indexOf('Person'))).toBe(false) + }) +})