Skip to content

Commit 960a853

Browse files
authored
(fix) silence false unused-export-let (#576)
TypeScript transpiles `export enum A` to `export var A`, which the compiler will warn about. Silence this edge case. #573
1 parent af6e743 commit 960a853

File tree

2 files changed

+110
-56
lines changed

2 files changed

+110
-56
lines changed

packages/language-server/src/plugins/svelte/features/getDiagnostics.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Warning } from 'svelte/types/compiler/interfaces';
22
import { Diagnostic, DiagnosticSeverity, Position, Range } from 'vscode-languageserver';
33
import { Document, isInTag, mapObjWithRangeToOriginal } from '../../../lib/documents';
44
import { Logger } from '../../../logger';
5-
import { SvelteDocument, TranspileErrorSource } from '../SvelteDocument';
65
import { CompilerWarningsSettings } from '../../../ls-config';
6+
import { SvelteDocument, TranspileErrorSource } from '../SvelteDocument';
77

88
/**
99
* Returns diagnostics from the svelte compiler.
@@ -53,7 +53,8 @@ async function tryGetDiagnostics(
5353
code: warning.code,
5454
};
5555
})
56-
.map((diag) => mapObjWithRangeToOriginal(transpiled, diag));
56+
.map((diag) => mapObjWithRangeToOriginal(transpiled, diag))
57+
.filter((diag) => isNoFalsePositive(diag, document));
5758
} catch (err) {
5859
return (await createParserErrorDiagnostic(err, document)).map((diag) =>
5960
mapObjWithRangeToOriginal(transpiled, diag),
@@ -241,5 +242,23 @@ function getErrorMessage(error: any, source: string, hint = '') {
241242
);
242243
}
243244

245+
function isNoFalsePositive(diag: Diagnostic, doc: Document): boolean {
246+
if (diag.code !== 'unused-export-let') {
247+
return true;
248+
}
249+
250+
// TypeScript transpiles `export enum A` to `export var A`, which the compiler will warn about.
251+
// Silence this edge case. We extract the property from the message and don't use the position
252+
// because that position could be wrong when source mapping trips up.
253+
const unusedExportName = diag.message.substring(
254+
diag.message.indexOf("'") + 1,
255+
diag.message.lastIndexOf("'"),
256+
);
257+
const hasExportedEnumWithThatName = new RegExp(
258+
`\\bexport\\s+?enum\\s+?${unusedExportName}\\b`,
259+
).test(doc.getText());
260+
return !hasExportedEnumWithThatName;
261+
}
262+
244263
const scssNodeRuntimeHint =
245264
'If you use SCSS, it may be necessary to add the path to your NODE runtime to the setting `svelte.language-server.runtime`, or use `sass` instead of `node-sass`. ';

packages/language-server/test/plugins/svelte/features/getDiagnostics.test.ts

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@ import { SvelteConfig } from '../../../../src/lib/documents/configLoader';
1010
import { CompilerWarningsSettings } from '../../../../src/ls-config';
1111

1212
describe('SveltePlugin#getDiagnostics', () => {
13-
async function expectDiagnosticsFor(
14-
getTranspiled: any,
15-
getCompiled: any,
16-
config: Partial<SvelteConfig>,
17-
settings: CompilerWarningsSettings = {},
18-
) {
19-
const document = new Document('', '<script></script>\n<style></style>');
13+
async function expectDiagnosticsFor({
14+
getTranspiled,
15+
getCompiled,
16+
config,
17+
settings = {},
18+
docText = '<script></script>\n<style></style>',
19+
}: {
20+
getTranspiled: any;
21+
getCompiled: any;
22+
config: Partial<SvelteConfig>;
23+
settings?: CompilerWarningsSettings;
24+
docText?: string;
25+
}) {
26+
const document = new Document('', docText);
2027
const svelteDoc: SvelteDocument = <any>{ getTranspiled, getCompiled, config };
2128
const result = await getDiagnostics(document, svelteDoc, settings);
2229
return {
@@ -26,13 +33,13 @@ describe('SveltePlugin#getDiagnostics', () => {
2633

2734
it('expect svelte.config.js error', async () => {
2835
(
29-
await expectDiagnosticsFor(
30-
() => {
36+
await expectDiagnosticsFor({
37+
getTranspiled: () => {
3138
throw new Error();
3239
},
33-
() => '',
34-
{ loadConfigError: new Error('svelte.config.js') },
35-
)
40+
getCompiled: () => '',
41+
config: { loadConfigError: new Error('svelte.config.js') },
42+
})
3643
).toEqual([
3744
{
3845
message: 'Error in svelte.config.js\n\nError: svelte.config.js',
@@ -54,15 +61,15 @@ describe('SveltePlugin#getDiagnostics', () => {
5461

5562
it('expect script transpilation error', async () => {
5663
(
57-
await expectDiagnosticsFor(
58-
() => {
64+
await expectDiagnosticsFor({
65+
getTranspiled: () => {
5966
const e: any = new Error('Script');
6067
e.__source = TranspileErrorSource.Script;
6168
throw e;
6269
},
63-
() => '',
64-
{},
65-
)
70+
getCompiled: () => '',
71+
config: {},
72+
})
6673
).toEqual([
6774
{
6875
message: 'Script',
@@ -84,15 +91,15 @@ describe('SveltePlugin#getDiagnostics', () => {
8491

8592
it('expect style transpilation error', async () => {
8693
(
87-
await expectDiagnosticsFor(
88-
() => {
94+
await expectDiagnosticsFor({
95+
getTranspiled: () => {
8996
const e: any = new Error('Style');
9097
e.__source = TranspileErrorSource.Style;
9198
throw e;
9299
},
93-
() => '',
94-
{},
95-
)
100+
getCompiled: () => '',
101+
config: {},
102+
})
96103
).toEqual([
97104
{
98105
message: 'Style',
@@ -114,17 +121,17 @@ describe('SveltePlugin#getDiagnostics', () => {
114121

115122
it('expect style transpilation error with line/columns', async () => {
116123
(
117-
await expectDiagnosticsFor(
118-
() => {
124+
await expectDiagnosticsFor({
125+
getTranspiled: () => {
119126
const e: any = new Error('Style');
120127
e.line = 1;
121128
e.column = 0;
122129
e.__source = TranspileErrorSource.Style;
123130
throw e;
124131
},
125-
() => '',
126-
{},
127-
)
132+
getCompiled: () => '',
133+
config: {},
134+
})
128135
).toEqual([
129136
{
130137
message: 'Style',
@@ -146,18 +153,18 @@ describe('SveltePlugin#getDiagnostics', () => {
146153

147154
it('expect compilation error', async () => {
148155
(
149-
await expectDiagnosticsFor(
150-
() => ({
156+
await expectDiagnosticsFor({
157+
getTranspiled: () => ({
151158
getOriginalPosition: () => Position.create(0, 0),
152159
}),
153-
() => {
160+
getCompiled: () => {
154161
const e: any = new Error('Compilation');
155162
e.message = 'ERROR';
156163
e.code = 123;
157164
throw e;
158165
},
159-
{},
160-
)
166+
config: {},
167+
})
161168
).toEqual([
162169
{
163170
code: 123,
@@ -180,19 +187,19 @@ describe('SveltePlugin#getDiagnostics', () => {
180187

181188
it('expect compilation error with expected', async () => {
182189
(
183-
await expectDiagnosticsFor(
184-
() => ({
190+
await expectDiagnosticsFor({
191+
getTranspiled: () => ({
185192
getOriginalPosition: () => Position.create(0, 8),
186193
}),
187-
() => {
194+
getCompiled: () => {
188195
const e: any = new Error('Compilation');
189196
e.message = 'expected x to not be here';
190197
e.code = 123;
191198
e.start = { line: 1, column: 8 };
192199
throw e;
193200
},
194-
{},
195-
)
201+
config: {},
202+
})
196203
).toEqual([
197204
{
198205
code: 123,
@@ -220,14 +227,14 @@ describe('SveltePlugin#getDiagnostics', () => {
220227

221228
it('expect warnings', async () => {
222229
(
223-
await expectDiagnosticsFor(
224-
() => ({
230+
await expectDiagnosticsFor({
231+
getTranspiled: () => ({
225232
getOriginalPosition: (pos: Position) => {
226233
pos.line - 1;
227234
return pos;
228235
},
229236
}),
230-
() =>
237+
getCompiled: () =>
231238
Promise.resolve({
232239
stats: {
233240
warnings: [
@@ -240,8 +247,8 @@ describe('SveltePlugin#getDiagnostics', () => {
240247
],
241248
},
242249
}),
243-
{},
244-
)
250+
config: {},
251+
})
245252
).toEqual([
246253
{
247254
code: 123,
@@ -262,16 +269,44 @@ describe('SveltePlugin#getDiagnostics', () => {
262269
]);
263270
});
264271

272+
it('filter out false positive warning (export enum)', async () => {
273+
(
274+
await expectDiagnosticsFor({
275+
docText: '<script context="module">export enum A { B }</script>',
276+
getTranspiled: () => ({
277+
getOriginalPosition: (pos: Position) => {
278+
return pos;
279+
},
280+
}),
281+
getCompiled: () =>
282+
Promise.resolve({
283+
stats: {
284+
warnings: [
285+
{
286+
start: { line: 0, column: 32 },
287+
end: { line: 0, column: 33 },
288+
message:
289+
"Component has unused export property 'A'. If it is for external reference only, please consider using `export const A`",
290+
code: 'unused-export-let',
291+
},
292+
],
293+
},
294+
}),
295+
config: {},
296+
})
297+
).toEqual([]);
298+
});
299+
265300
it('filter out warnings', async () => {
266301
(
267-
await expectDiagnosticsFor(
268-
() => ({
302+
await expectDiagnosticsFor({
303+
getTranspiled: () => ({
269304
getOriginalPosition: (pos: Position) => {
270305
pos.line - 1;
271306
return pos;
272307
},
273308
}),
274-
() =>
309+
getCompiled: () =>
275310
Promise.resolve({
276311
stats: {
277312
warnings: [
@@ -284,22 +319,22 @@ describe('SveltePlugin#getDiagnostics', () => {
284319
],
285320
},
286321
}),
287-
{},
288-
{ '123': 'ignore' },
289-
)
322+
config: {},
323+
settings: { '123': 'ignore' },
324+
})
290325
).toEqual([]);
291326
});
292327

293328
it('treat warnings as error', async () => {
294329
(
295-
await expectDiagnosticsFor(
296-
() => ({
330+
await expectDiagnosticsFor({
331+
getTranspiled: () => ({
297332
getOriginalPosition: (pos: Position) => {
298333
pos.line - 1;
299334
return pos;
300335
},
301336
}),
302-
() =>
337+
getCompiled: () =>
303338
Promise.resolve({
304339
stats: {
305340
warnings: [
@@ -312,9 +347,9 @@ describe('SveltePlugin#getDiagnostics', () => {
312347
],
313348
},
314349
}),
315-
{},
316-
{ '123': 'error' },
317-
)
350+
config: {},
351+
settings: { '123': 'error' },
352+
})
318353
).toEqual([
319354
{
320355
code: '123',

0 commit comments

Comments
 (0)