@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.
- package/CHANGELOG.md +23 -0
- package/lib/createTypeScriptFileFromProgram.js +4 -3
- package/lib/createTypeScriptFileFromProjectService.d.ts +9 -2
- package/lib/createTypeScriptFileFromProjectService.js +6 -3
- package/lib/directives/parseDirectivesFromTypeScriptFile.d.ts +5 -0
- package/lib/directives/parseDirectivesFromTypeScriptFile.js +21 -0
- package/lib/directives/parseDirectivesFromTypeScriptFile.test.d.ts +1 -0
- package/lib/directives/parseDirectivesFromTypeScriptFile.test.js +91 -0
- package/lib/getTSNodeRange.d.ts +3 -0
- package/lib/{getNodeRange.js → getTSNodeRange.js} +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/language.js +7 -4
- package/lib/plugin.d.ts +6 -8
- package/lib/plugin.js +1 -1
- package/lib/prepareTypeScriptFile.d.ts +7 -0
- package/lib/prepareTypeScriptFile.js +7 -0
- package/lib/rules/consecutiveNonNullAssertions.js +17 -15
- package/lib/rules/forInArrays.js +13 -11
- package/lib/rules/namespaceDeclarations.js +17 -15
- package/package.json +1 -1
- package/src/createTypeScriptFileFromProgram.ts +6 -5
- package/src/createTypeScriptFileFromProjectService.ts +7 -5
- package/src/directives/parseDirectivesFromTypeScriptFile.test.ts +108 -0
- package/src/directives/parseDirectivesFromTypeScriptFile.ts +31 -0
- package/src/{getNodeRange.ts → getTSNodeRange.ts} +1 -1
- package/src/index.ts +2 -1
- package/src/language.ts +17 -8
- package/src/plugin.ts +1 -1
- package/src/prepareTypeScriptFile.ts +14 -0
- package/src/rules/consecutiveNonNullAssertions.ts +17 -15
- package/src/rules/forInArrays.ts +16 -14
- package/src/rules/namespaceDeclarations.ts +24 -19
- package/tsconfig.tsbuildinfo +1 -1
- 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
|
|
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):
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
languageFile: {
|
|
13
|
+
...file,
|
|
14
|
+
[Symbol.dispose]() {
|
|
15
|
+
service.closeClientFile(filePathAbsolute);
|
|
16
|
+
},
|
|
15
17
|
},
|
|
18
|
+
sourceFile,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
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
|
-
|
|
46
|
+
prepareFromDisk: (filePathAbsolute) => {
|
|
46
47
|
const program = servicePrograms.get(filePathAbsolute);
|
|
47
48
|
seenPrograms.add(program);
|
|
48
|
-
|
|
49
|
+
const { languageFile, sourceFile } = createTypeScriptFileFromProjectService(filePathAbsolute, program, service);
|
|
50
|
+
return prepareTypeScriptFile(languageFile, sourceFile);
|
|
49
51
|
},
|
|
50
|
-
|
|
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
|
-
|
|
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").
|
|
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
|
|
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
|
-
|
|
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
|
+
};
|
|
@@ -17,22 +17,24 @@ export default typescriptLanguage.createRule({
|
|
|
17
17
|
},
|
|
18
18
|
setup(context) {
|
|
19
19
|
return {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
message: "consecutiveNonNullAssertion",
|
|
34
|
-
range,
|
|
35
|
-
});
|
|
36
|
+
});
|
|
37
|
+
},
|
|
36
38
|
},
|
|
37
39
|
};
|
|
38
40
|
},
|
package/lib/rules/forInArrays.js
CHANGED
|
@@ -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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 {
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
node.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
import {
|
|
2
2
|
LanguageFileDefinition,
|
|
3
|
-
|
|
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:
|
|
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
|
|
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
|
-
)
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
}
|