Skip to content

Commit 5f2e749

Browse files
authored
silence some incorrect unsafe-member-access errors (#88)
1 parent e8d997d commit 5f2e749

File tree

8 files changed

+206
-27
lines changed

8 files changed

+206
-27
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,24 @@ module.exports = {
102102
};
103103
```
104104

105+
There are some limitations to these type-aware rules currently. Specifically, checks in the context of reactive assignments and store subscriptions will report false positives or false negatives, depending on the rule. In the case of reactive assignments, you can work around this by explicitly typing the reactive variable. An example with the `no-unsafe-member-access` rule:
106+
107+
```svelte
108+
<script lang="ts">
109+
import { writable } from 'svelte/store';
110+
111+
const store = writable([]);
112+
$store.length; // incorrect no-unsafe-member-access error
113+
114+
$: assignment = [];
115+
assignment.length; // incorrect no-unsafe-member-access error
116+
// You can work around this by doing
117+
let another_assignment: string[];
118+
$: another_assignment = [];
119+
another_assignment.length; // OK
120+
</script>
121+
```
122+
105123
## Interactions with other plugins
106124

107125
Care needs to be taken when using this plugin alongside others. Take a look at [this list of things you need to watch out for](OTHER_PLUGINS.md).

src/preprocess.js

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,12 @@ export const preprocess = text => {
111111
const block = new_block();
112112
state.blocks.set(with_file_ending('instance'), block);
113113

114-
block.transformed_code = vars.filter(v => v.injected || v.module).map(v => `let ${v.name};`).join('');
114+
if (ast.module && processor_options.typescript) {
115+
block.transformed_code = vars.filter(v => v.injected).map(v => `let ${v.name};`).join('');
116+
block.transformed_code += text.slice(ast.module.content.start, ast.module.content.end);
117+
} else {
118+
block.transformed_code = vars.filter(v => v.injected || v.module).map(v => `let ${v.name};`).join('');
119+
}
115120

116121
get_translation(text, block, ast.instance.content);
117122

@@ -123,7 +128,19 @@ export const preprocess = text => {
123128
const block = new_block();
124129
state.blocks.set(with_file_ending('template'), block);
125130

126-
block.transformed_code = vars.map(v => `let ${v.name};`).join('');
131+
if (processor_options.typescript) {
132+
block.transformed_code = '';
133+
if (ast.module) {
134+
block.transformed_code += text.slice(ast.module.content.start, ast.module.content.end);
135+
}
136+
if (ast.instance) {
137+
block.transformed_code += '\n';
138+
block.transformed_code += vars.filter(v => v.injected).map(v => `let ${v.name};`).join('');
139+
block.transformed_code += text.slice(ast.instance.content.start, ast.instance.content.end);
140+
}
141+
} else {
142+
block.transformed_code = vars.map(v => `let ${v.name};`).join('');
143+
}
127144

128145
const nodes_with_contextual_scope = new WeakSet();
129146
let in_quoted_attribute = false;
@@ -209,14 +226,10 @@ const ts_import_transformer = (context) => {
209226
// 5. parse the source to get the AST
210227
// 6. return AST of step 5, warnings and vars of step 2
211228
function compile_code(text, compiler, processor_options) {
212-
let ast;
213-
let warnings;
214-
let vars;
215-
216229
const ts = processor_options.typescript;
217-
let mapper;
218-
let ts_result;
219-
if (ts) {
230+
if (!ts) {
231+
return compiler.compile(text, { generate: false, ...processor_options.compiler_options });
232+
} else {
220233
const diffs = [];
221234
let accumulated_diff = 0;
222235
const transpiled = text.replace(/<script(\s[^]*?)?>([^]*?)<\/script>/gi, (match, attributes = '', content) => {
@@ -245,7 +258,9 @@ function compile_code(text, compiler, processor_options) {
245258
});
246259
return `<script${attributes}>${output.outputText}</script>`;
247260
});
248-
mapper = new DocumentMapper(text, transpiled, diffs);
261+
const mapper = new DocumentMapper(text, transpiled, diffs);
262+
263+
let ts_result;
249264
try {
250265
ts_result = compiler.compile(transpiled, { generate: false, ...processor_options.compiler_options });
251266
} catch (err) {
@@ -263,16 +278,10 @@ function compile_code(text, compiler, processor_options) {
263278
.replace(/[^\n][^\n][^\n][^\n]\n/g, '/**/\n')
264279
}</script>`;
265280
});
266-
}
267-
268-
if (!ts) {
269-
({ ast, warnings, vars } = compiler.compile(text, { generate: false, ...processor_options.compiler_options }));
270-
} else {
271281
// if we do a full recompile Svelte can fail due to the blank script tag not declaring anything
272282
// so instead we just parse for the AST (which is likely faster, anyways)
273-
ast = compiler.parse(text, { ...processor_options.compiler_options });
274-
({ warnings, vars } = ts_result);
283+
const ast = compiler.parse(text, { ...processor_options.compiler_options });
284+
const{ warnings, vars } = ts_result;
285+
return { ast, warnings, vars, mapper };
275286
}
276-
277-
return { ast, warnings, vars, mapper };
278287
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = {
2+
parser: "@typescript-eslint/parser",
3+
plugins: [
4+
"@typescript-eslint",
5+
],
6+
parserOptions: {
7+
project: ["./tsconfig.json"],
8+
tsconfigRootDir: __dirname,
9+
extraFileExtensions: [".svelte"],
10+
},
11+
settings: {
12+
"svelte3/typescript": require("typescript"),
13+
},
14+
rules: {
15+
"@typescript-eslint/no-unsafe-member-access": "error",
16+
},
17+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script lang="ts" context="module">
2+
const context_safe = [];
3+
const context_unsafe: any = null;
4+
5+
console.log(context_safe.length);
6+
console.log(context_unsafe.length);
7+
</script>
8+
9+
<script lang="ts">
10+
import { writable } from 'svelte/store';
11+
import { external_safe, external_unsafe } from './external-file';
12+
const instance_safe = [];
13+
const instance_unsafe: any = null;
14+
$: reactive_safe = instance_safe;
15+
$: reactive_unsafe = instance_unsafe;
16+
const writable_safe = writable([]);
17+
const writable_unsafe = writable(null as any);
18+
19+
console.log(context_safe.length);
20+
console.log(context_unsafe.length);
21+
console.log(external_safe.length);
22+
console.log(external_unsafe.length);
23+
console.log(instance_safe.length);
24+
console.log(instance_unsafe.length);
25+
// console.log(reactive_safe.length); TODO, current limitation
26+
console.log(reactive_unsafe.length);
27+
// console.log($writable_safe.length); TODO, current limitation
28+
console.log($writable_unsafe.length);
29+
</script>
30+
31+
{context_safe.length}
32+
{context_unsafe.length}
33+
{external_safe.length}
34+
{external_unsafe.length}
35+
{instance_safe.length}
36+
{instance_unsafe.length}
37+
<!-- {reactive_safe.length} TODO, current limitation -->
38+
{reactive_unsafe.length}
39+
<!-- {$writable_safe.length} TODO, current limitation -->
40+
{$writable_unsafe.length}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
[
2+
{
3+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
4+
"severity": 2,
5+
"line": 6,
6+
"column": 14,
7+
"endLine": 6,
8+
"endColumn": 35
9+
},
10+
{
11+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
12+
"severity": 2,
13+
"line": 20,
14+
"column": 14,
15+
"endLine": 20,
16+
"endColumn": 35
17+
},
18+
{
19+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
20+
"severity": 2,
21+
"line": 22,
22+
"column": 14,
23+
"endLine": 22,
24+
"endColumn": 36
25+
},
26+
{
27+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
28+
"severity": 2,
29+
"line": 24,
30+
"column": 14,
31+
"endLine": 24,
32+
"endColumn": 36
33+
},
34+
{
35+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
36+
"severity": 2,
37+
"line": 26,
38+
"column": 14,
39+
"endLine": 26,
40+
"endColumn": 36
41+
},
42+
{
43+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
44+
"severity": 2,
45+
"line": 28,
46+
"column": 14,
47+
"endLine": 28,
48+
"endColumn": 37
49+
},
50+
{
51+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
52+
"severity": 2,
53+
"line": 32,
54+
"column": 2,
55+
"endLine": 32,
56+
"endColumn": 23
57+
},
58+
{
59+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
60+
"severity": 2,
61+
"line": 34,
62+
"column": 2,
63+
"endLine": 34,
64+
"endColumn": 24
65+
},
66+
{
67+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
68+
"severity": 2,
69+
"line": 36,
70+
"column": 2,
71+
"endLine": 36,
72+
"endColumn": 24
73+
},
74+
{
75+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
76+
"severity": 2,
77+
"line": 38,
78+
"column": 2,
79+
"endLine": 38,
80+
"endColumn": 24
81+
},
82+
{
83+
"ruleId": "@typescript-eslint/no-unsafe-member-access",
84+
"severity": 2,
85+
"line": 40,
86+
"column": 2,
87+
"endLine": 40,
88+
"endColumn": 25
89+
}
90+
]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const external_safe = ['hi'];
2+
export const external_unsafe: any = null;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"include": ["**/*"]
3+
}

test/samples/typescript/expected.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
"messageId": "suggestUnknown",
3636
"fix": {
3737
"range": [
38-
29,
39-
32
38+
58,
39+
61
4040
],
4141
"text": "unknown"
4242
},
@@ -46,8 +46,8 @@
4646
"messageId": "suggestNever",
4747
"fix": {
4848
"range": [
49-
29,
50-
32
49+
58,
50+
61
5151
],
5252
"text": "never"
5353
},
@@ -82,8 +82,8 @@
8282
"messageId": "suggestUnknown",
8383
"fix": {
8484
"range": [
85-
50,
86-
53
85+
79,
86+
82
8787
],
8888
"text": "unknown"
8989
},
@@ -93,8 +93,8 @@
9393
"messageId": "suggestNever",
9494
"fix": {
9595
"range": [
96-
50,
97-
53
96+
79,
97+
82
9898
],
9999
"text": "never"
100100
},

0 commit comments

Comments
 (0)