@flint.fyi/ts 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/lib/createTypeScriptFileFromProgram.js +4 -3
  3. package/lib/createTypeScriptFileFromProjectService.d.ts +9 -2
  4. package/lib/createTypeScriptFileFromProjectService.js +6 -3
  5. package/lib/directives/parseDirectivesFromTypeScriptFile.d.ts +5 -0
  6. package/lib/directives/parseDirectivesFromTypeScriptFile.js +21 -0
  7. package/lib/directives/parseDirectivesFromTypeScriptFile.test.d.ts +1 -0
  8. package/lib/directives/parseDirectivesFromTypeScriptFile.test.js +91 -0
  9. package/lib/getTSNodeRange.d.ts +3 -0
  10. package/lib/{getNodeRange.js → getTSNodeRange.js} +1 -1
  11. package/lib/index.d.ts +2 -1
  12. package/lib/index.js +2 -1
  13. package/lib/language.js +7 -4
  14. package/lib/plugin.d.ts +6 -8
  15. package/lib/plugin.js +1 -1
  16. package/lib/prepareTypeScriptFile.d.ts +7 -0
  17. package/lib/prepareTypeScriptFile.js +7 -0
  18. package/lib/rules/consecutiveNonNullAssertions.js +17 -15
  19. package/lib/rules/forInArrays.js +13 -11
  20. package/lib/rules/namespaceDeclarations.js +17 -15
  21. package/package.json +1 -1
  22. package/src/createTypeScriptFileFromProgram.ts +6 -5
  23. package/src/createTypeScriptFileFromProjectService.ts +7 -5
  24. package/src/directives/parseDirectivesFromTypeScriptFile.test.ts +108 -0
  25. package/src/directives/parseDirectivesFromTypeScriptFile.ts +31 -0
  26. package/src/{getNodeRange.ts → getTSNodeRange.ts} +1 -1
  27. package/src/index.ts +2 -1
  28. package/src/language.ts +17 -8
  29. package/src/plugin.ts +1 -1
  30. package/src/prepareTypeScriptFile.ts +14 -0
  31. package/src/rules/consecutiveNonNullAssertions.ts +17 -15
  32. package/src/rules/forInArrays.ts +16 -14
  33. package/src/rules/namespaceDeclarations.ts +24 -19
  34. package/tsconfig.tsbuildinfo +1 -1
  35. package/lib/getNodeRange.d.ts +0 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @flint/ts
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7d0d873: add // flint-\* comment directives
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [b48f4a9]
12
+ - Updated dependencies [7d0d873]
13
+ - Updated dependencies [79f15da]
14
+ - @flint.fyi/core@0.15.0
15
+ - @flint.fyi/rule-tester@0.14.0
16
+
17
+ ## 0.13.2
18
+
19
+ ### Patch Changes
20
+
21
+ - 0b80834: allow rules to indicate dependencies
22
+ - Updated dependencies [0b80834]
23
+ - Updated dependencies [63b61e5]
24
+ - @flint.fyi/core@0.13.5
25
+
3
26
  ## 0.13.1
4
27
 
5
28
  ### Patch Changes
@@ -28,7 +28,7 @@ export function createTypeScriptFileFromProgram(program, sourceFile) {
28
28
  }),
29
29
  }));
30
30
  },
31
- runRule(rule, options) {
31
+ async runRule(rule, options) {
32
32
  const reports = [];
33
33
  const context = {
34
34
  report: (report) => {
@@ -41,10 +41,11 @@ export function createTypeScriptFileFromProgram(program, sourceFile) {
41
41
  sourceFile,
42
42
  typeChecker: program.getTypeChecker(),
43
43
  };
44
- const visitors = rule.setup(context, options);
45
- if (!visitors) {
44
+ const runtime = await rule.setup(context, options);
45
+ if (!runtime?.visitors) {
46
46
  return reports;
47
47
  }
48
+ const { visitors } = runtime;
48
49
  const visit = (node) => {
49
50
  visitors[ts.SyntaxKind[node.kind]]?.(node);
50
51
  node.forEachChild(visit);
@@ -1,3 +1,10 @@
1
- import { LanguageFileDefinition } from "@flint.fyi/core";
2
1
  import * as ts from "typescript";
3
- export declare function createTypeScriptFileFromProjectService(filePathAbsolute: string, program: ts.Program, service: ts.server.ProjectService): LanguageFileDefinition;
2
+ export declare function createTypeScriptFileFromProjectService(filePathAbsolute: string, program: ts.Program, service: ts.server.ProjectService): {
3
+ languageFile: {
4
+ [Symbol.dispose](): void;
5
+ cache?: import("@flint.fyi/core").LanguageFileCacheImpacts;
6
+ getDiagnostics?(): import("@flint.fyi/core").LanguageDiagnostics;
7
+ runRule<OptionsSchema extends import("@flint.fyi/core").AnyOptionalSchema | undefined = import("@flint.fyi/core").AnyOptionalSchema | undefined>(rule: import("@flint.fyi/core").AnyRuleDefinition<OptionsSchema>, options: import("@flint.fyi/core").InferredObject<OptionsSchema>): import("@flint.fyi/core/lib/types/promises.js").PromiseOrSync<import("@flint.fyi/core").NormalizedReport[]>;
8
+ };
9
+ sourceFile: ts.SourceFile;
10
+ };
@@ -9,9 +9,12 @@ export function createTypeScriptFileFromProjectService(filePathAbsolute, program
9
9
  log("Retrieved source file and type checker for file %s:", filePathAbsolute);
10
10
  const file = createTypeScriptFileFromProgram(program, sourceFile);
11
11
  return {
12
- ...file,
13
- [Symbol.dispose]() {
14
- service.closeClientFile(filePathAbsolute);
12
+ languageFile: {
13
+ ...file,
14
+ [Symbol.dispose]() {
15
+ service.closeClientFile(filePathAbsolute);
16
+ },
15
17
  },
18
+ sourceFile,
16
19
  };
17
20
  }
@@ -0,0 +1,5 @@
1
+ import ts from "typescript";
2
+ export declare function parseDirectivesFromTypeScriptFile(sourceFile: ts.SourceFile): {
3
+ directives: import("@flint.fyi/core").CommentDirective[];
4
+ reports: import("@flint.fyi/core").FileReport[];
5
+ };
@@ -0,0 +1,21 @@
1
+ import { DirectivesCollector } from "@flint.fyi/core";
2
+ import * as tsutils from "ts-api-utils";
3
+ import { normalizeRange } from "../normalizeRange.js";
4
+ export function parseDirectivesFromTypeScriptFile(sourceFile) {
5
+ const collector = new DirectivesCollector(sourceFile.statements.at(0)?.getStart(sourceFile) ?? sourceFile.text.length);
6
+ tsutils.forEachComment(sourceFile, (fullText, sourceRange) => {
7
+ const commentText = fullText.slice(sourceRange.pos, sourceRange.end);
8
+ const match = /^\/\/\s*flint-(\S+)(?:\s+(.+))?/.exec(commentText);
9
+ if (!match) {
10
+ return;
11
+ }
12
+ const commentRange = {
13
+ begin: sourceRange.pos,
14
+ end: sourceRange.end,
15
+ };
16
+ const range = normalizeRange(commentRange, sourceFile);
17
+ const [type, selection] = match.slice(1);
18
+ collector.add(range, selection, type);
19
+ });
20
+ return collector.collect();
21
+ }
@@ -0,0 +1,91 @@
1
+ import ts from "typescript";
2
+ import { describe, expect, it } from "vitest";
3
+ import { parseDirectivesFromTypeScriptFile } from "./parseDirectivesFromTypeScriptFile.js";
4
+ describe(parseDirectivesFromTypeScriptFile, () => {
5
+ it("returns empty arrays when there are no directives", () => {
6
+ const sourceFile = ts.createSourceFile("test.ts", "// unrelated", ts.ScriptTarget.ESNext, true);
7
+ const actual = parseDirectivesFromTypeScriptFile(sourceFile);
8
+ expect(actual).toEqual({
9
+ directives: [],
10
+ reports: [],
11
+ });
12
+ });
13
+ it("returns parsed directives when there are comment directives", () => {
14
+ const sourceFile = ts.createSourceFile("test.ts", `
15
+ // flint-disable-file a
16
+ // flint-disable-next-line b
17
+ // flint-invalid
18
+ `, ts.ScriptTarget.ESNext, true);
19
+ const actual = parseDirectivesFromTypeScriptFile(sourceFile);
20
+ expect(actual).toMatchInlineSnapshot(`
21
+ {
22
+ "directives": [
23
+ {
24
+ "range": {
25
+ "begin": {
26
+ "column": 16,
27
+ "line": 1,
28
+ "raw": 17,
29
+ },
30
+ "end": {
31
+ "column": 39,
32
+ "line": 1,
33
+ "raw": 40,
34
+ },
35
+ },
36
+ "selections": [
37
+ "a",
38
+ ],
39
+ "type": "disable-file",
40
+ },
41
+ {
42
+ "range": {
43
+ "begin": {
44
+ "column": 16,
45
+ "line": 2,
46
+ "raw": 57,
47
+ },
48
+ "end": {
49
+ "column": 44,
50
+ "line": 2,
51
+ "raw": 85,
52
+ },
53
+ },
54
+ "selections": [
55
+ "b",
56
+ ],
57
+ "type": "disable-next-line",
58
+ },
59
+ ],
60
+ "reports": [
61
+ {
62
+ "about": {
63
+ "id": "commentDirectiveUnknown",
64
+ },
65
+ "message": {
66
+ "primary": "Unknown comment directive type: "invalid".",
67
+ "secondary": [
68
+ "TODO",
69
+ ],
70
+ "suggestions": [
71
+ "TODO",
72
+ ],
73
+ },
74
+ "range": {
75
+ "begin": {
76
+ "column": 16,
77
+ "line": 3,
78
+ "raw": 102,
79
+ },
80
+ "end": {
81
+ "column": 32,
82
+ "line": 3,
83
+ "raw": 118,
84
+ },
85
+ },
86
+ },
87
+ ],
88
+ }
89
+ `);
90
+ });
91
+ });
@@ -0,0 +1,3 @@
1
+ import type * as ts from "typescript";
2
+ import { CharacterReportRange } from "@flint.fyi/core";
3
+ export declare function getTSNodeRange(node: ts.Node, sourceFile: ts.SourceFile): CharacterReportRange;
@@ -1,4 +1,4 @@
1
- export function getNodeRange(node, sourceFile) {
1
+ export function getTSNodeRange(node, sourceFile) {
2
2
  return {
3
3
  begin: node.getStart(sourceFile),
4
4
  end: node.getEnd(),
package/lib/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { typescriptLanguage } from "./language.js";
1
+ export { getTSNodeRange } from "./getTSNodeRange.js";
2
+ export * from "./language.js";
2
3
  export { ts } from "./plugin.js";
package/lib/index.js CHANGED
@@ -1,2 +1,3 @@
1
- export { typescriptLanguage } from "./language.js";
1
+ export { getTSNodeRange } from "./getTSNodeRange.js";
2
+ export * from "./language.js";
2
3
  export { ts } from "./plugin.js";
package/lib/language.js CHANGED
@@ -7,6 +7,7 @@ import path from "node:path";
7
7
  import * as ts from "typescript";
8
8
  import { createTypeScriptFileFromProgram } from "./createTypeScriptFileFromProgram.js";
9
9
  import { createTypeScriptFileFromProjectService } from "./createTypeScriptFileFromProjectService.js";
10
+ import { prepareTypeScriptFile } from "./prepareTypeScriptFile.js";
10
11
  const log = debugForFile(import.meta.filename);
11
12
  const projectRoot = path.join(import.meta.dirname, "../..");
12
13
  export const typescriptLanguage = createLanguage({
@@ -42,12 +43,13 @@ export const typescriptLanguage = createLanguage({
42
43
  return program;
43
44
  });
44
45
  return {
45
- prepareFileOnDisk: (filePathAbsolute) => {
46
+ prepareFromDisk: (filePathAbsolute) => {
46
47
  const program = servicePrograms.get(filePathAbsolute);
47
48
  seenPrograms.add(program);
48
- return createTypeScriptFileFromProjectService(filePathAbsolute, program, service);
49
+ const { languageFile, sourceFile } = createTypeScriptFileFromProjectService(filePathAbsolute, program, service);
50
+ return prepareTypeScriptFile(languageFile, sourceFile);
49
51
  },
50
- prepareFileVirtually: (filePathAbsolute, sourceText) => {
52
+ prepareFromVirtual: (filePathAbsolute, sourceText) => {
51
53
  const environment = environments.get(filePathAbsolute);
52
54
  environment.updateFile(filePathAbsolute, sourceText);
53
55
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
@@ -55,7 +57,8 @@ export const typescriptLanguage = createLanguage({
55
57
  const program = environment.languageService.getProgram();
56
58
  /* eslint-enable @typescript-eslint/no-non-null-assertion */
57
59
  seenPrograms.add(program);
58
- return createTypeScriptFileFromProgram(program, sourceFile);
60
+ const languageFile = createTypeScriptFileFromProgram(program, sourceFile);
61
+ return prepareTypeScriptFile(languageFile, sourceFile);
59
62
  },
60
63
  };
61
64
  },
package/lib/plugin.d.ts CHANGED
@@ -1,15 +1,13 @@
1
- export declare const ts: import("@flint.fyi/core").Plugin<import("@flint.fyi/core").RuleAbout, {
2
- all: string[];
3
- }, (import("@flint.fyi/core").Rule<{
4
- readonly id: "consecutiveNonNullAssertions";
5
- readonly preset: "logical";
6
- }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "consecutiveNonNullAssertion", undefined> | import("@flint.fyi/core").Rule<{
1
+ export declare const ts: import("@flint.fyi/core").Plugin<import("@flint.fyi/core").BaseAbout, string | undefined, [import("@flint.fyi/core").Rule<{
7
2
  readonly id: "forInArrays";
8
3
  readonly preset: "logical";
9
- }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "forIn", undefined> | import("@flint.fyi/core").Rule<{
4
+ }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "forIn", undefined>, import("@flint.fyi/core").Rule<{
5
+ readonly id: "consecutiveNonNullAssertions";
6
+ readonly preset: "logical";
7
+ }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "consecutiveNonNullAssertion", undefined>, import("@flint.fyi/core").Rule<{
10
8
  readonly id: "namespaceDeclarations";
11
9
  readonly preset: "logical";
12
10
  }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "preferModules", {
13
11
  readonly allowDeclarations: import("zod").ZodDefault<import("zod").ZodBoolean>;
14
12
  readonly allowDefinitionFiles: import("zod").ZodDefault<import("zod").ZodBoolean>;
15
- }>)[]>;
13
+ }>]>;
package/lib/plugin.js CHANGED
@@ -3,7 +3,7 @@ import consecutiveNonNullAssertions from "./rules/consecutiveNonNullAssertions.j
3
3
  import forInArrays from "./rules/forInArrays.js";
4
4
  import namespaceDeclarations from "./rules/namespaceDeclarations.js";
5
5
  export const ts = createPlugin({
6
- globs: {
6
+ files: {
7
7
  all: ["**/*.{cjs,js,jsx,mjs,ts,tsx}"],
8
8
  },
9
9
  name: "ts",
@@ -0,0 +1,7 @@
1
+ import { LanguageFileDefinition } from "@flint.fyi/core";
2
+ import ts from "typescript";
3
+ export declare function prepareTypeScriptFile(languageFile: LanguageFileDefinition, sourceFile: ts.SourceFile): {
4
+ file: LanguageFileDefinition;
5
+ directives: import("@flint.fyi/core").CommentDirective[];
6
+ reports: import("@flint.fyi/core").FileReport[];
7
+ };
@@ -0,0 +1,7 @@
1
+ import { parseDirectivesFromTypeScriptFile } from "./directives/parseDirectivesFromTypeScriptFile.js";
2
+ export function prepareTypeScriptFile(languageFile, sourceFile) {
3
+ return {
4
+ ...parseDirectivesFromTypeScriptFile(sourceFile),
5
+ file: languageFile,
6
+ };
7
+ }
@@ -17,22 +17,24 @@ export default typescriptLanguage.createRule({
17
17
  },
18
18
  setup(context) {
19
19
  return {
20
- NonNullExpression(node) {
21
- if (node.parent.kind !== ts.SyntaxKind.NonNullExpression) {
22
- return;
23
- }
24
- const range = {
25
- begin: node.end,
26
- end: node.parent.end + 1,
27
- };
28
- context.report({
29
- fix: {
20
+ visitors: {
21
+ NonNullExpression: (node) => {
22
+ if (node.parent.kind !== ts.SyntaxKind.NonNullExpression) {
23
+ return;
24
+ }
25
+ const range = {
26
+ begin: node.end,
27
+ end: node.parent.end + 1,
28
+ };
29
+ context.report({
30
+ fix: {
31
+ range,
32
+ text: "",
33
+ },
34
+ message: "consecutiveNonNullAssertion",
30
35
  range,
31
- text: "",
32
- },
33
- message: "consecutiveNonNullAssertion",
34
- range,
35
- });
36
+ });
37
+ },
36
38
  },
37
39
  };
38
40
  },
@@ -33,17 +33,19 @@ export default typescriptLanguage.createRule({
33
33
  return isTypeRecursive(type, (t) => t.getNumberIndexType() != null && hasNumberLikeLength(t));
34
34
  }
35
35
  return {
36
- ForInStatement(node) {
37
- const type = getConstrainedTypeAtLocation(node.expression, context.typeChecker);
38
- if (isArrayLike(type)) {
39
- context.report({
40
- message: "forIn",
41
- range: {
42
- begin: node.getStart(),
43
- end: node.statement.getStart() - 1,
44
- },
45
- });
46
- }
36
+ visitors: {
37
+ ForInStatement: (node) => {
38
+ const type = getConstrainedTypeAtLocation(node.expression, context.typeChecker);
39
+ if (isArrayLike(type)) {
40
+ context.report({
41
+ message: "forIn",
42
+ range: {
43
+ begin: node.getStart(),
44
+ end: node.statement.getStart() - 1,
45
+ },
46
+ });
47
+ }
48
+ },
47
49
  },
48
50
  };
49
51
  },
@@ -1,7 +1,7 @@
1
1
  import * as tsutils from "ts-api-utils";
2
2
  import * as ts from "typescript";
3
3
  import { z } from "zod";
4
- import { getNodeRange } from "../getNodeRange.js";
4
+ import { getTSNodeRange } from "../getTSNodeRange.js";
5
5
  import { typescriptLanguage } from "../language.js";
6
6
  export default typescriptLanguage.createRule({
7
7
  about: {
@@ -28,20 +28,22 @@ export default typescriptLanguage.createRule({
28
28
  return;
29
29
  }
30
30
  return {
31
- ModuleDeclaration(node) {
32
- if (node.parent.kind !== ts.SyntaxKind.SourceFile ||
33
- node.name.kind !== ts.SyntaxKind.Identifier ||
34
- node.name.text === "global") {
35
- return;
36
- }
37
- if (allowDeclarations &&
38
- tsutils.includesModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword)) {
39
- return;
40
- }
41
- context.report({
42
- message: "preferModules",
43
- range: getNodeRange(node.getChildAt(0), context.sourceFile),
44
- });
31
+ visitors: {
32
+ ModuleDeclaration: (node) => {
33
+ if (node.parent.kind !== ts.SyntaxKind.SourceFile ||
34
+ node.name.kind !== ts.SyntaxKind.Identifier ||
35
+ node.name.text === "global") {
36
+ return;
37
+ }
38
+ if (allowDeclarations &&
39
+ tsutils.includesModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword)) {
40
+ return;
41
+ }
42
+ context.report({
43
+ message: "preferModules",
44
+ range: getTSNodeRange(node.getChildAt(0), context.sourceFile),
45
+ });
46
+ },
45
47
  },
46
48
  };
47
49
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flint.fyi/ts",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "[Experimental] TypeScript language plugin for Flint.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LanguageFileDefinition,
3
- NormalizedRuleReport,
3
+ NormalizedReport,
4
4
  RuleReport,
5
5
  } from "@flint.fyi/core";
6
6
  import * as ts from "typescript";
@@ -42,8 +42,8 @@ export function createTypeScriptFileFromProgram(
42
42
  }),
43
43
  }));
44
44
  },
45
- runRule(rule, options) {
46
- const reports: NormalizedRuleReport[] = [];
45
+ async runRule(rule, options) {
46
+ const reports: NormalizedReport[] = [];
47
47
 
48
48
  const context = {
49
49
  report: (report: RuleReport) => {
@@ -57,12 +57,13 @@ export function createTypeScriptFileFromProgram(
57
57
  typeChecker: program.getTypeChecker(),
58
58
  };
59
59
 
60
- const visitors = rule.setup(context, options);
60
+ const runtime = await rule.setup(context, options);
61
61
 
62
- if (!visitors) {
62
+ if (!runtime?.visitors) {
63
63
  return reports;
64
64
  }
65
65
 
66
+ const { visitors } = runtime;
66
67
  const visit = (node: ts.Node) => {
67
68
  visitors[ts.SyntaxKind[node.kind]]?.(node);
68
69
 
@@ -1,4 +1,3 @@
1
- import { LanguageFileDefinition } from "@flint.fyi/core";
2
1
  import { debugForFile } from "debug-for-file";
3
2
  import * as ts from "typescript";
4
3
 
@@ -10,7 +9,7 @@ export function createTypeScriptFileFromProjectService(
10
9
  filePathAbsolute: string,
11
10
  program: ts.Program,
12
11
  service: ts.server.ProjectService,
13
- ): LanguageFileDefinition {
12
+ ) {
14
13
  const sourceFile = program.getSourceFile(filePathAbsolute);
15
14
  if (!sourceFile) {
16
15
  throw new Error(`Could not retrieve source file for: ${filePathAbsolute}`);
@@ -21,9 +20,12 @@ export function createTypeScriptFileFromProjectService(
21
20
  const file = createTypeScriptFileFromProgram(program, sourceFile);
22
21
 
23
22
  return {
24
- ...file,
25
- [Symbol.dispose]() {
26
- service.closeClientFile(filePathAbsolute);
23
+ languageFile: {
24
+ ...file,
25
+ [Symbol.dispose]() {
26
+ service.closeClientFile(filePathAbsolute);
27
+ },
27
28
  },
29
+ sourceFile,
28
30
  };
29
31
  }
@@ -0,0 +1,108 @@
1
+ import ts from "typescript";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { parseDirectivesFromTypeScriptFile } from "./parseDirectivesFromTypeScriptFile.js";
5
+
6
+ describe(parseDirectivesFromTypeScriptFile, () => {
7
+ it("returns empty arrays when there are no directives", () => {
8
+ const sourceFile = ts.createSourceFile(
9
+ "test.ts",
10
+ "// unrelated",
11
+ ts.ScriptTarget.ESNext,
12
+ true,
13
+ );
14
+
15
+ const actual = parseDirectivesFromTypeScriptFile(sourceFile);
16
+
17
+ expect(actual).toEqual({
18
+ directives: [],
19
+ reports: [],
20
+ });
21
+ });
22
+
23
+ it("returns parsed directives when there are comment directives", () => {
24
+ const sourceFile = ts.createSourceFile(
25
+ "test.ts",
26
+ `
27
+ // flint-disable-file a
28
+ // flint-disable-next-line b
29
+ // flint-invalid
30
+ `,
31
+ ts.ScriptTarget.ESNext,
32
+ true,
33
+ );
34
+
35
+ const actual = parseDirectivesFromTypeScriptFile(sourceFile);
36
+
37
+ expect(actual).toMatchInlineSnapshot(`
38
+ {
39
+ "directives": [
40
+ {
41
+ "range": {
42
+ "begin": {
43
+ "column": 16,
44
+ "line": 1,
45
+ "raw": 17,
46
+ },
47
+ "end": {
48
+ "column": 39,
49
+ "line": 1,
50
+ "raw": 40,
51
+ },
52
+ },
53
+ "selections": [
54
+ "a",
55
+ ],
56
+ "type": "disable-file",
57
+ },
58
+ {
59
+ "range": {
60
+ "begin": {
61
+ "column": 16,
62
+ "line": 2,
63
+ "raw": 57,
64
+ },
65
+ "end": {
66
+ "column": 44,
67
+ "line": 2,
68
+ "raw": 85,
69
+ },
70
+ },
71
+ "selections": [
72
+ "b",
73
+ ],
74
+ "type": "disable-next-line",
75
+ },
76
+ ],
77
+ "reports": [
78
+ {
79
+ "about": {
80
+ "id": "commentDirectiveUnknown",
81
+ },
82
+ "message": {
83
+ "primary": "Unknown comment directive type: "invalid".",
84
+ "secondary": [
85
+ "TODO",
86
+ ],
87
+ "suggestions": [
88
+ "TODO",
89
+ ],
90
+ },
91
+ "range": {
92
+ "begin": {
93
+ "column": 16,
94
+ "line": 3,
95
+ "raw": 102,
96
+ },
97
+ "end": {
98
+ "column": 32,
99
+ "line": 3,
100
+ "raw": 118,
101
+ },
102
+ },
103
+ },
104
+ ],
105
+ }
106
+ `);
107
+ });
108
+ });
@@ -0,0 +1,31 @@
1
+ import { DirectivesCollector } from "@flint.fyi/core";
2
+ import * as tsutils from "ts-api-utils";
3
+ import ts from "typescript";
4
+
5
+ import { normalizeRange } from "../normalizeRange.js";
6
+
7
+ export function parseDirectivesFromTypeScriptFile(sourceFile: ts.SourceFile) {
8
+ const collector = new DirectivesCollector(
9
+ sourceFile.statements.at(0)?.getStart(sourceFile) ?? sourceFile.text.length,
10
+ );
11
+
12
+ tsutils.forEachComment(sourceFile, (fullText, sourceRange) => {
13
+ const commentText = fullText.slice(sourceRange.pos, sourceRange.end);
14
+ const match = /^\/\/\s*flint-(\S+)(?:\s+(.+))?/.exec(commentText);
15
+ if (!match) {
16
+ return;
17
+ }
18
+
19
+ const commentRange = {
20
+ begin: sourceRange.pos,
21
+ end: sourceRange.end,
22
+ };
23
+
24
+ const range = normalizeRange(commentRange, sourceFile);
25
+ const [type, selection] = match.slice(1);
26
+
27
+ collector.add(range, selection, type);
28
+ });
29
+
30
+ return collector.collect();
31
+ }
@@ -2,7 +2,7 @@ import type * as ts from "typescript";
2
2
 
3
3
  import { CharacterReportRange } from "@flint.fyi/core";
4
4
 
5
- export function getNodeRange(
5
+ export function getTSNodeRange(
6
6
  node: ts.Node,
7
7
  sourceFile: ts.SourceFile,
8
8
  ): CharacterReportRange {