@flint.fyi/ts 0.13.2 → 0.14.1
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 +20 -0
- package/lib/createTypeScriptFileFromProgram.js +3 -1
- 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/getFirstEnumValues.d.ts +35 -0
- package/lib/getFirstEnumValues.js +51 -0
- package/lib/getFirstEnumValues.test.d.ts +1 -0
- package/lib/getFirstEnumValues.test.js +45 -0
- package/lib/language.js +7 -4
- package/lib/plugin.d.ts +3 -0
- package/lib/prepareTypeScriptFile.d.ts +7 -0
- package/lib/prepareTypeScriptFile.js +7 -0
- package/lib/rules/consecutiveNonNullAssertions.d.ts +1 -0
- package/lib/rules/consecutiveNonNullAssertions.js +3 -2
- package/lib/rules/forInArrays.d.ts +1 -0
- package/lib/rules/forInArrays.js +1 -0
- package/lib/rules/namespaceDeclarations.d.ts +1 -0
- package/lib/rules/namespaceDeclarations.js +9 -2
- package/package.json +1 -1
- package/src/createTypeScriptFileFromProgram.ts +6 -3
- package/src/createTypeScriptFileFromProjectService.ts +7 -5
- package/src/directives/parseDirectivesFromTypeScriptFile.test.ts +108 -0
- package/src/directives/parseDirectivesFromTypeScriptFile.ts +31 -0
- package/src/getFirstEnumValues.test.ts +51 -0
- package/src/getFirstEnumValues.ts +59 -0
- package/src/language.ts +17 -8
- package/src/prepareTypeScriptFile.ts +14 -0
- package/src/rules/consecutiveNonNullAssertions.ts +3 -2
- package/src/rules/forInArrays.ts +1 -0
- package/src/rules/namespaceDeclarations.ts +13 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { getFirstEnumValues } from "./getFirstEnumValues.js";
|
|
4
|
+
|
|
5
|
+
describe("getFirstEnumValues", () => {
|
|
6
|
+
it("returns an empty object when given an empty enum", () => {
|
|
7
|
+
enum Empty {}
|
|
8
|
+
|
|
9
|
+
const result = getFirstEnumValues(Empty);
|
|
10
|
+
|
|
11
|
+
expect(result).toEqual({});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns all original pairs when when given a non-repeating enum", () => {
|
|
15
|
+
enum NonRepeating {
|
|
16
|
+
A = 1,
|
|
17
|
+
B = 2,
|
|
18
|
+
C = 3,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = getFirstEnumValues(NonRepeating);
|
|
22
|
+
|
|
23
|
+
expect(result).toEqual({
|
|
24
|
+
1: "A",
|
|
25
|
+
2: "B",
|
|
26
|
+
3: "C",
|
|
27
|
+
A: 1,
|
|
28
|
+
B: 2,
|
|
29
|
+
C: 3,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns the first key when when given a repeating-value enum", () => {
|
|
34
|
+
enum Repeating {
|
|
35
|
+
A = 1,
|
|
36
|
+
B = 2,
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/prefer-literal-enum-member
|
|
38
|
+
C = A,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = getFirstEnumValues(Repeating);
|
|
42
|
+
|
|
43
|
+
expect(result).toEqual({
|
|
44
|
+
1: "A",
|
|
45
|
+
2: "B",
|
|
46
|
+
A: 1,
|
|
47
|
+
B: 2,
|
|
48
|
+
C: 1,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes TypeScript enum values that alias earlier values.
|
|
3
|
+
* @param original Any TypeScript enum with numeric values.
|
|
4
|
+
* @returns A version of the enum with only the first occurrence of each value.
|
|
5
|
+
* @example
|
|
6
|
+
* Given the following enum:
|
|
7
|
+
* ```ts
|
|
8
|
+
* enum Example {
|
|
9
|
+
* A = 1,
|
|
10
|
+
* B = 2,
|
|
11
|
+
* C = A
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
* Its original value looks like:
|
|
15
|
+
* ```ts
|
|
16
|
+
* {
|
|
17
|
+
* 1: 'C', // <-- Points to 'C', the alias of 'A'
|
|
18
|
+
* 2: 'B',
|
|
19
|
+
* A: 1,
|
|
20
|
+
* B: 2,
|
|
21
|
+
* C: 1,
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
* Calling `getFirstEnumValues(Example)` would return:
|
|
25
|
+
* ```ts
|
|
26
|
+
* {
|
|
27
|
+
* 1: 'A', // <-- Corrected to the original 'A'
|
|
28
|
+
* 2: 'B',
|
|
29
|
+
* A: 1,
|
|
30
|
+
* B: 2,
|
|
31
|
+
* C: 1,
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function getFirstEnumValues<
|
|
36
|
+
Keys extends string,
|
|
37
|
+
Values extends number,
|
|
38
|
+
Original extends Record<Keys | Values, Keys | Values>,
|
|
39
|
+
>(original: Original) {
|
|
40
|
+
const result = {} as Original;
|
|
41
|
+
|
|
42
|
+
for (const key in original) {
|
|
43
|
+
if (typeof original[key] !== "number") {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!(key in result)) {
|
|
48
|
+
result[key] = original[key];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!(original[key] in result)) {
|
|
52
|
+
// 🤷 enums are tricky to type.
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
54
|
+
result[original[key]] = key as any;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
}
|
package/src/language.ts
CHANGED
|
@@ -12,6 +12,7 @@ import * as ts from "typescript";
|
|
|
12
12
|
import { createTypeScriptFileFromProgram } from "./createTypeScriptFileFromProgram.js";
|
|
13
13
|
import { createTypeScriptFileFromProjectService } from "./createTypeScriptFileFromProjectService.js";
|
|
14
14
|
import { TSNodesByName } from "./nodes.js";
|
|
15
|
+
import { prepareTypeScriptFile } from "./prepareTypeScriptFile.js";
|
|
15
16
|
|
|
16
17
|
const log = debugForFile(import.meta.filename);
|
|
17
18
|
|
|
@@ -84,18 +85,21 @@ export const typescriptLanguage = createLanguage<
|
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
return {
|
|
87
|
-
|
|
88
|
+
prepareFromDisk: (filePathAbsolute) => {
|
|
88
89
|
const program = servicePrograms.get(filePathAbsolute);
|
|
89
90
|
|
|
90
91
|
seenPrograms.add(program);
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const { languageFile, sourceFile } =
|
|
94
|
+
createTypeScriptFileFromProjectService(
|
|
95
|
+
filePathAbsolute,
|
|
96
|
+
program,
|
|
97
|
+
service,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return prepareTypeScriptFile(languageFile, sourceFile);
|
|
97
101
|
},
|
|
98
|
-
|
|
102
|
+
prepareFromVirtual: (filePathAbsolute, sourceText) => {
|
|
99
103
|
const environment = environments.get(filePathAbsolute);
|
|
100
104
|
environment.updateFile(filePathAbsolute, sourceText);
|
|
101
105
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
@@ -105,7 +109,12 @@ export const typescriptLanguage = createLanguage<
|
|
|
105
109
|
|
|
106
110
|
seenPrograms.add(program);
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
const languageFile = createTypeScriptFileFromProgram(
|
|
113
|
+
program,
|
|
114
|
+
sourceFile,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return prepareTypeScriptFile(languageFile, sourceFile);
|
|
109
118
|
},
|
|
110
119
|
};
|
|
111
120
|
},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LanguageFileDefinition } from "@flint.fyi/core";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
|
|
4
|
+
import { parseDirectivesFromTypeScriptFile } from "./directives/parseDirectivesFromTypeScriptFile.js";
|
|
5
|
+
|
|
6
|
+
export function prepareTypeScriptFile(
|
|
7
|
+
languageFile: LanguageFileDefinition,
|
|
8
|
+
sourceFile: ts.SourceFile,
|
|
9
|
+
) {
|
|
10
|
+
return {
|
|
11
|
+
...parseDirectivesFromTypeScriptFile(sourceFile),
|
|
12
|
+
file: languageFile,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -4,6 +4,7 @@ import { typescriptLanguage } from "../language.js";
|
|
|
4
4
|
|
|
5
5
|
export default typescriptLanguage.createRule({
|
|
6
6
|
about: {
|
|
7
|
+
description: "Reports unnecessary extra non-null assertions.",
|
|
7
8
|
id: "consecutiveNonNullAssertions",
|
|
8
9
|
preset: "logical",
|
|
9
10
|
},
|
|
@@ -26,8 +27,8 @@ export default typescriptLanguage.createRule({
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const range = {
|
|
29
|
-
begin: node.end,
|
|
30
|
-
end: node.parent.end
|
|
30
|
+
begin: node.end - 1,
|
|
31
|
+
end: node.parent.end,
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
context.report({
|
package/src/rules/forInArrays.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { typescriptLanguage } from "../language.js";
|
|
|
7
7
|
|
|
8
8
|
export default typescriptLanguage.createRule({
|
|
9
9
|
about: {
|
|
10
|
+
description: "Reports using legacy `namespace` declarations.",
|
|
10
11
|
id: "namespaceDeclarations",
|
|
11
12
|
preset: "logical",
|
|
12
13
|
},
|
|
@@ -23,8 +24,18 @@ export default typescriptLanguage.createRule({
|
|
|
23
24
|
},
|
|
24
25
|
},
|
|
25
26
|
options: {
|
|
26
|
-
allowDeclarations: z
|
|
27
|
-
|
|
27
|
+
allowDeclarations: z
|
|
28
|
+
.boolean()
|
|
29
|
+
.default(false)
|
|
30
|
+
.describe(
|
|
31
|
+
"Whether to allow namespaces declared with the `declare` keyword.",
|
|
32
|
+
),
|
|
33
|
+
allowDefinitionFiles: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.default(false)
|
|
36
|
+
.describe(
|
|
37
|
+
"Whether to allow namespaces in `.d.ts` and other definition files.",
|
|
38
|
+
),
|
|
28
39
|
},
|
|
29
40
|
setup(context, { allowDeclarations, allowDefinitionFiles }) {
|
|
30
41
|
if (allowDefinitionFiles && context.sourceFile.isDeclarationFile) {
|