@flint.fyi/ts 0.14.1 → 0.14.2

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @flint/ts
2
2
 
3
+ ## 0.14.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 6415134: feat: implement debuggerStatements rule for TypeScript plugin
8
+ - Updated dependencies [b58d145]
9
+ - @flint.fyi/rule-tester@0.14.1
10
+
3
11
  ## 0.14.1
4
12
 
5
13
  ### Patch Changes
package/lib/plugin.d.ts CHANGED
@@ -1,16 +1,24 @@
1
1
  export declare const ts: import("@flint.fyi/core").Plugin<import("@flint.fyi/core").RuleAbout, string | undefined, [import("@flint.fyi/core").Rule<{
2
- readonly description: "Reports iterating over an array with a for-in loop.";
3
- readonly id: "forInArrays";
4
- readonly preset: "logical";
5
- }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "forIn", undefined>, import("@flint.fyi/core").Rule<{
6
2
  readonly description: "Reports unnecessary extra non-null assertions.";
7
3
  readonly id: "consecutiveNonNullAssertions";
8
4
  readonly preset: "logical";
9
5
  }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "consecutiveNonNullAssertion", undefined>, import("@flint.fyi/core").Rule<{
6
+ readonly description: "Reports using debugger statements.";
7
+ readonly id: "debuggerStatements";
8
+ readonly preset: "logical";
9
+ }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "noDebugger", undefined>, import("@flint.fyi/core").Rule<{
10
+ readonly description: "Reports iterating over an array with a for-in loop.";
11
+ readonly id: "forInArrays";
12
+ readonly preset: "logical";
13
+ }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "forIn", undefined>, import("@flint.fyi/core").Rule<{
10
14
  readonly description: "Reports using legacy `namespace` declarations.";
11
15
  readonly id: "namespaceDeclarations";
12
16
  readonly preset: "logical";
13
17
  }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "preferModules", {
14
18
  readonly allowDeclarations: import("zod").ZodDefault<import("zod").ZodBoolean>;
15
19
  readonly allowDefinitionFiles: import("zod").ZodDefault<import("zod").ZodBoolean>;
16
- }>]>;
20
+ }>, import("@flint.fyi/core").Rule<{
21
+ readonly description: "Reports using octal escape sequences in string literals.";
22
+ readonly id: "octalEscapes";
23
+ readonly preset: "logical";
24
+ }, import("./nodes.js").TSNodesByName, import("./language.js").TypeScriptServices, "noOctalEscape", undefined>]>;
package/lib/plugin.js CHANGED
@@ -1,11 +1,19 @@
1
1
  import { createPlugin } from "@flint.fyi/core";
2
2
  import consecutiveNonNullAssertions from "./rules/consecutiveNonNullAssertions.js";
3
+ import debuggerStatements from "./rules/debuggerStatements.js";
3
4
  import forInArrays from "./rules/forInArrays.js";
4
5
  import namespaceDeclarations from "./rules/namespaceDeclarations.js";
6
+ import octalEscapes from "./rules/octalEscapes.js";
5
7
  export const ts = createPlugin({
6
8
  files: {
7
9
  all: ["**/*.{cjs,js,jsx,mjs,ts,tsx}"],
8
10
  },
9
11
  name: "ts",
10
- rules: [forInArrays, consecutiveNonNullAssertions, namespaceDeclarations],
12
+ rules: [
13
+ consecutiveNonNullAssertions,
14
+ debuggerStatements,
15
+ forInArrays,
16
+ namespaceDeclarations,
17
+ octalEscapes,
18
+ ],
11
19
  });
@@ -0,0 +1,6 @@
1
+ declare const _default: import("@flint.fyi/core").Rule<{
2
+ readonly description: "Reports using debugger statements.";
3
+ readonly id: "debuggerStatements";
4
+ readonly preset: "logical";
5
+ }, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "noDebugger", undefined>;
6
+ export default _default;
@@ -0,0 +1,43 @@
1
+ import { typescriptLanguage } from "../language.js";
2
+ export default typescriptLanguage.createRule({
3
+ about: {
4
+ description: "Reports using debugger statements.",
5
+ id: "debuggerStatements",
6
+ preset: "logical",
7
+ },
8
+ messages: {
9
+ noDebugger: {
10
+ primary: "Debugger statements should not be used in production code.",
11
+ secondary: [
12
+ "The `debugger` statement causes the JavaScript runtime to pause execution and start a debugger if one is available, such as when browser developer tools are open.",
13
+ "This can be useful during development, but should not be left in production code.",
14
+ ],
15
+ suggestions: [
16
+ "Remove the `debugger` statement before shipping this code to users.",
17
+ ],
18
+ },
19
+ },
20
+ setup(context) {
21
+ return {
22
+ visitors: {
23
+ DebuggerStatement: (node) => {
24
+ const range = {
25
+ begin: node.getStart(),
26
+ end: node.getEnd(),
27
+ };
28
+ context.report({
29
+ message: "noDebugger",
30
+ range,
31
+ suggestions: [
32
+ {
33
+ id: "removeDebugger",
34
+ range,
35
+ text: "",
36
+ },
37
+ ],
38
+ });
39
+ },
40
+ },
41
+ };
42
+ },
43
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ import rule from "./debuggerStatements.js";
2
+ import { ruleTester } from "./ruleTester.js";
3
+ ruleTester.describe(rule, {
4
+ invalid: [
5
+ {
6
+ code: `
7
+ debugger;
8
+ `,
9
+ snapshot: `
10
+ debugger;
11
+ ~~~~~~~~~
12
+ Debugger statements should not be used in production code.
13
+ `,
14
+ suggestions: [
15
+ {
16
+ id: "removeDebugger",
17
+ updated: `
18
+
19
+ `,
20
+ },
21
+ ],
22
+ },
23
+ {
24
+ code: `
25
+ function test() {
26
+ debugger;
27
+ }
28
+ `,
29
+ snapshot: `
30
+ function test() {
31
+ debugger;
32
+ ~~~~~~~~~
33
+ Debugger statements should not be used in production code.
34
+ }
35
+ `,
36
+ suggestions: [
37
+ {
38
+ id: "removeDebugger",
39
+ updated: `
40
+ function test() {
41
+
42
+ }
43
+ `,
44
+ },
45
+ ],
46
+ },
47
+ {
48
+ code: `
49
+ if (condition) {
50
+ debugger;
51
+ }
52
+ `,
53
+ snapshot: `
54
+ if (condition) {
55
+ debugger;
56
+ ~~~~~~~~~
57
+ Debugger statements should not be used in production code.
58
+ }
59
+ `,
60
+ suggestions: [
61
+ {
62
+ id: "removeDebugger",
63
+ updated: `
64
+ if (condition) {
65
+
66
+ }
67
+ `,
68
+ },
69
+ ],
70
+ },
71
+ ],
72
+ valid: [
73
+ `console.log("debugging");`,
74
+ `function test() { console.log("test"); }`,
75
+ `for (let i = 0; i < 10; i++) { break; }`,
76
+ `for (let i = 0; i < 10; i++) { continue; }`,
77
+ `function test() { return; }`,
78
+ `throw new Error("test");`,
79
+ ],
80
+ });
@@ -0,0 +1,6 @@
1
+ declare const _default: import("@flint.fyi/core").Rule<{
2
+ readonly description: "Reports using octal escape sequences in string literals.";
3
+ readonly id: "octalEscapes";
4
+ readonly preset: "logical";
5
+ }, import("../nodes.js").TSNodesByName, import("../language.js").TypeScriptServices, "noOctalEscape", undefined>;
6
+ export default _default;
@@ -0,0 +1,71 @@
1
+ import { typescriptLanguage } from "../language.js";
2
+ /**
3
+ * Finds the position and length of an octal escape sequence in a string.
4
+ * Returns undefined if no octal escape is found.
5
+ */
6
+ function findOctalEscape(text) {
7
+ // Remove quotes from the string literal
8
+ const content = text.slice(1, -1);
9
+ // Match octal escapes: \0 followed by [0-7], or \1-7 optionally followed by [0-7]
10
+ // But exclude escaped backslashes (\\)
11
+ const octalEscapePattern = /(?<!\\)\\0[0-7]|(?<!\\)\\[1-7][0-7]*/;
12
+ const match = octalEscapePattern.exec(content);
13
+ if (!match) {
14
+ return undefined;
15
+ }
16
+ // Add 1 to account for the opening quote
17
+ return {
18
+ index: match.index + 1,
19
+ length: match[0].length,
20
+ };
21
+ }
22
+ export default typescriptLanguage.createRule({
23
+ about: {
24
+ description: "Reports using octal escape sequences in string literals.",
25
+ id: "octalEscapes",
26
+ preset: "logical",
27
+ },
28
+ messages: {
29
+ noOctalEscape: {
30
+ primary: "Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.",
31
+ secondary: [
32
+ "Octal escape sequences (e.g., `\\01`, `\\02`, `\\377`) are a deprecated feature in JavaScript that can lead to confusion and are forbidden in strict mode and template literals.",
33
+ "They are less readable than their modern alternatives and can cause portability issues.",
34
+ ],
35
+ suggestions: [
36
+ "Use hexadecimal escape sequences (e.g., `\\x01`) or Unicode escape sequences (e.g., `\\u0001`) instead.",
37
+ ],
38
+ },
39
+ },
40
+ setup(context) {
41
+ function checkNode(node) {
42
+ const text = node.getText(context.sourceFile);
43
+ const octalEscape = findOctalEscape(text);
44
+ if (!octalEscape) {
45
+ return;
46
+ }
47
+ const nodeStart = node.getStart(context.sourceFile);
48
+ context.report({
49
+ message: "noOctalEscape",
50
+ range: {
51
+ begin: nodeStart + octalEscape.index,
52
+ end: nodeStart + octalEscape.index + octalEscape.length,
53
+ },
54
+ });
55
+ }
56
+ return {
57
+ visitors: {
58
+ NoSubstitutionTemplateLiteral: checkNode,
59
+ StringLiteral: checkNode,
60
+ TemplateExpression: (node) => {
61
+ // Check the head of the template
62
+ checkNode(node.head);
63
+ // Check each template span
64
+ for (const span of node.templateSpans) {
65
+ checkNode(span.literal);
66
+ }
67
+ },
68
+ },
69
+ };
70
+ },
71
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,213 @@
1
+ import rule from "./octalEscapes.js";
2
+ import { ruleTester } from "./ruleTester.js";
3
+ ruleTester.describe(rule, {
4
+ invalid: [
5
+ {
6
+ code: `
7
+ const a = "\\1";
8
+ `,
9
+ snapshot: `
10
+ const a = "\\1";
11
+ ~~
12
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
13
+ `,
14
+ },
15
+ {
16
+ code: `
17
+ const a = "\\2";
18
+ `,
19
+ snapshot: `
20
+ const a = "\\2";
21
+ ~~
22
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
23
+ `,
24
+ },
25
+ {
26
+ code: `
27
+ const a = "\\7";
28
+ `,
29
+ snapshot: `
30
+ const a = "\\7";
31
+ ~~
32
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
33
+ `,
34
+ },
35
+ {
36
+ code: `
37
+ const a = "\\00";
38
+ `,
39
+ snapshot: `
40
+ const a = "\\00";
41
+ ~~~
42
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
43
+ `,
44
+ },
45
+ {
46
+ code: `
47
+ const a = "\\01";
48
+ `,
49
+ snapshot: `
50
+ const a = "\\01";
51
+ ~~~
52
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
53
+ `,
54
+ },
55
+ {
56
+ code: `
57
+ const a = "\\02";
58
+ `,
59
+ snapshot: `
60
+ const a = "\\02";
61
+ ~~~
62
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
63
+ `,
64
+ },
65
+ {
66
+ code: `
67
+ const a = "\\07";
68
+ `,
69
+ snapshot: `
70
+ const a = "\\07";
71
+ ~~~
72
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
73
+ `,
74
+ },
75
+ {
76
+ code: `
77
+ const a = "\\377";
78
+ `,
79
+ snapshot: `
80
+ const a = "\\377";
81
+ ~~~~
82
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
83
+ `,
84
+ },
85
+ {
86
+ code: `
87
+ const a = "\\12";
88
+ `,
89
+ snapshot: `
90
+ const a = "\\12";
91
+ ~~~
92
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
93
+ `,
94
+ },
95
+ {
96
+ code: `
97
+ const a = "before\\1after";
98
+ `,
99
+ snapshot: `
100
+ const a = "before\\1after";
101
+ ~~
102
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
103
+ `,
104
+ },
105
+ {
106
+ code: `
107
+ const a = "before\\01after";
108
+ `,
109
+ snapshot: `
110
+ const a = "before\\01after";
111
+ ~~~
112
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
113
+ `,
114
+ },
115
+ {
116
+ code: `
117
+ const a = '\\1';
118
+ `,
119
+ snapshot: `
120
+ const a = '\\1';
121
+ ~~
122
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
123
+ `,
124
+ },
125
+ {
126
+ code: `
127
+ const a = '\\01';
128
+ `,
129
+ snapshot: `
130
+ const a = '\\01';
131
+ ~~~
132
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
133
+ `,
134
+ },
135
+ {
136
+ code: `
137
+ const a = \`\\1\`;
138
+ `,
139
+ snapshot: `
140
+ const a = \`\\1\`;
141
+ ~~
142
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
143
+ `,
144
+ },
145
+ {
146
+ code: `
147
+ const a = \`\\01\`;
148
+ `,
149
+ snapshot: `
150
+ const a = \`\\01\`;
151
+ ~~~
152
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
153
+ `,
154
+ },
155
+ {
156
+ code: `
157
+ const a = \`before\\1after\`;
158
+ `,
159
+ snapshot: `
160
+ const a = \`before\\1after\`;
161
+ ~~
162
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
163
+ `,
164
+ },
165
+ {
166
+ code: `
167
+ const x = 5;
168
+ const a = \`value: \${x} \\1\`;
169
+ `,
170
+ snapshot: `
171
+ const x = 5;
172
+ const a = \`value: \${x} \\1\`;
173
+ ~~
174
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
175
+ `,
176
+ },
177
+ {
178
+ code: `
179
+ const x = 5;
180
+ const a = \`\\01 value: \${x}\`;
181
+ `,
182
+ snapshot: `
183
+ const x = 5;
184
+ const a = \`\\01 value: \${x}\`;
185
+ ~~~
186
+ Prefer hexadecimal or Unicode escape sequences over legacy octal escape sequences.
187
+ `,
188
+ },
189
+ ],
190
+ valid: [
191
+ `const a = "\\0";`,
192
+ `const a = "\\8";`,
193
+ `const a = "\\9";`,
194
+ `const a = "\\x01";`,
195
+ `const a = "\\u0001";`,
196
+ `const a = "\\n";`,
197
+ `const a = "\\t";`,
198
+ `const a = "\\\\0";`,
199
+ `const a = "\\\\1";`,
200
+ `const a = "before\\0after";`,
201
+ `const a = "before\\8after";`,
202
+ `const a = "before\\9after";`,
203
+ `const a = '\\0';`,
204
+ `const a = '\\8';`,
205
+ `const a = '\\9';`,
206
+ `const a = \`\\0\`;`,
207
+ `const a = \`\\8\`;`,
208
+ `const a = \`\\9\`;`,
209
+ `const x = 5; const a = \`value: \${x} \\0\`;`,
210
+ `const x = 5; const a = \`\\0 value: \${x}\`;`,
211
+ `const x = 5; const a = \`\\8 \${x} \\9\`;`,
212
+ ],
213
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flint.fyi/ts",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "[Experimental] TypeScript language plugin for Flint.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/plugin.ts CHANGED
@@ -1,13 +1,21 @@
1
1
  import { createPlugin } from "@flint.fyi/core";
2
2
 
3
3
  import consecutiveNonNullAssertions from "./rules/consecutiveNonNullAssertions.js";
4
+ import debuggerStatements from "./rules/debuggerStatements.js";
4
5
  import forInArrays from "./rules/forInArrays.js";
5
6
  import namespaceDeclarations from "./rules/namespaceDeclarations.js";
7
+ import octalEscapes from "./rules/octalEscapes.js";
6
8
 
7
9
  export const ts = createPlugin({
8
10
  files: {
9
11
  all: ["**/*.{cjs,js,jsx,mjs,ts,tsx}"],
10
12
  },
11
13
  name: "ts",
12
- rules: [forInArrays, consecutiveNonNullAssertions, namespaceDeclarations],
14
+ rules: [
15
+ consecutiveNonNullAssertions,
16
+ debuggerStatements,
17
+ forInArrays,
18
+ namespaceDeclarations,
19
+ octalEscapes,
20
+ ],
13
21
  });
@@ -0,0 +1,81 @@
1
+ import rule from "./debuggerStatements.js";
2
+ import { ruleTester } from "./ruleTester.js";
3
+
4
+ ruleTester.describe(rule, {
5
+ invalid: [
6
+ {
7
+ code: `
8
+ debugger;
9
+ `,
10
+ snapshot: `
11
+ debugger;
12
+ ~~~~~~~~~
13
+ Debugger statements should not be used in production code.
14
+ `,
15
+ suggestions: [
16
+ {
17
+ id: "removeDebugger",
18
+ updated: `
19
+
20
+ `,
21
+ },
22
+ ],
23
+ },
24
+ {
25
+ code: `
26
+ function test() {
27
+ debugger;
28
+ }
29
+ `,
30
+ snapshot: `
31
+ function test() {
32
+ debugger;
33
+ ~~~~~~~~~
34
+ Debugger statements should not be used in production code.
35
+ }
36
+ `,
37
+ suggestions: [
38
+ {
39
+ id: "removeDebugger",
40
+ updated: `
41
+ function test() {
42
+
43
+ }
44
+ `,
45
+ },
46
+ ],
47
+ },
48
+ {
49
+ code: `
50
+ if (condition) {
51
+ debugger;
52
+ }
53
+ `,
54
+ snapshot: `
55
+ if (condition) {
56
+ debugger;
57
+ ~~~~~~~~~
58
+ Debugger statements should not be used in production code.
59
+ }
60
+ `,
61
+ suggestions: [
62
+ {
63
+ id: "removeDebugger",
64
+ updated: `
65
+ if (condition) {
66
+
67
+ }
68
+ `,
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ valid: [
74
+ `console.log("debugging");`,
75
+ `function test() { console.log("test"); }`,
76
+ `for (let i = 0; i < 10; i++) { break; }`,
77
+ `for (let i = 0; i < 10; i++) { continue; }`,
78
+ `function test() { return; }`,
79
+ `throw new Error("test");`,
80
+ ],
81
+ });
@@ -0,0 +1,45 @@
1
+ import { typescriptLanguage } from "../language.js";
2
+
3
+ export default typescriptLanguage.createRule({
4
+ about: {
5
+ description: "Reports using debugger statements.",
6
+ id: "debuggerStatements",
7
+ preset: "logical",
8
+ },
9
+ messages: {
10
+ noDebugger: {
11
+ primary: "Debugger statements should not be used in production code.",
12
+ secondary: [
13
+ "The `debugger` statement causes the JavaScript runtime to pause execution and start a debugger if one is available, such as when browser developer tools are open.",
14
+ "This can be useful during development, but should not be left in production code.",
15
+ ],
16
+ suggestions: [
17
+ "Remove the `debugger` statement before shipping this code to users.",
18
+ ],
19
+ },
20
+ },
21
+ setup(context) {
22
+ return {
23
+ visitors: {
24
+ DebuggerStatement: (node) => {
25
+ const range = {
26
+ begin: node.getStart(),
27
+ end: node.getEnd(),
28
+ };
29
+
30
+ context.report({
31
+ message: "noDebugger",
32
+ range,
33
+ suggestions: [
34
+ {
35
+ id: "removeDebugger",
36
+ range,
37
+ text: "",
38
+ },
39
+ ],
40
+ });
41
+ },
42
+ },
43
+ };
44
+ },
45
+ });