Skip to content

Commit 5e7f0f0

Browse files
committed
Merge branch 'master' of https://github.com/sveltejs/language-tools into event-type-check
2 parents b3ff362 + e84e0e2 commit 5e7f0f0

File tree

41 files changed

+336
-1859
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+336
-1859
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ Pull requests are encouraged and always welcome. [Pick an issue](https://github.
7373

7474
To install and work on these tools locally:
7575

76+
> Make sure to uninstall the extension from the marketplace to not have it clash with the local one.
77+
7678
```bash
7779
git clone https://github.com/sveltejs/language-tools.git svelte-language-tools
7880
cd svelte-language-tools
@@ -102,7 +104,7 @@ To run the developer version of both the language server and the VSCode extensio
102104
- Go to the debugging panel
103105
- Make sure "Run VSCode Extension" is selected, and hit run
104106

105-
This launches a new VSCode window and a watcher for your changes. In this dev window you can choose an existing Svelte project to work against. If you don't use pure Javascript and CSS, but languages like Typescript or SCSS, your project will need a [Svelte preprocessor setup](packages/svelte-vscode#using-with-preprocessors). When you make changes to the extension or language server you can use the command "Reload Window" in the VSCode command palette to see your changes.
107+
This launches a new VSCode window and a watcher for your changes. In this dev window you can choose an existing Svelte project to work against. If you don't use pure Javascript and CSS, but languages like Typescript or SCSS, your project will need a [Svelte preprocessor setup](packages/svelte-vscode#using-with-preprocessors). When you make changes to the extension or language server you can use the command "Reload Window" in the VSCode command palette to see your changes. When you make changes to `svelte2tsx`, you first need to run `yarn build` within its folder.
106108

107109
### Running Tests
108110

docs/preprocessors/typescript.md

-34
Original file line numberDiff line numberDiff line change
@@ -126,37 +126,3 @@ $: show = !!data.someKey; // <-- `show` now has type `boolean`
126126
### Can I use TypeScript syntax inside the template/mustache tags?
127127

128128
At the moment, you cannot. Only `script`/`style` tags are preprocessed/transpiled. See [this issue](https://github.com/sveltejs/svelte/issues/4701) for more info.
129-
130-
### I get weird type errors on my html tags
131-
132-
This may be due to some library you are using having installed typings for `react`. These are picked up by the TypeScript compiler. Because we internally transform svelte to jsx, there is a clash and said error occurs.
133-
134-
![image](https://user-images.githubusercontent.com/374638/85633868-72697280-b67a-11ea-8f8c-7fe2b4702339.png)
135-
136-
The underlying [issue in TypeScript](https://github.com/microsoft/TypeScript/issues/18588) is yet to be fixed but in the meantime, one way to work around it is as follows:
137-
138-
1. Add a `sink.d.ts` with content `declare module 'react' {}`
139-
2. Go to your `tsconfig.json`
140-
3. If you do not have such a setting already, enhance `compilerOptions` with `"baseUrl": "."`
141-
4. Enhance `compilerOptions` with `"paths": { "react": ["<path to your sink.d.ts, relative to the baseUrl>"] }`
142-
143-
`tsconfig.json`
144-
145-
```json
146-
{
147-
"compilerOptions": {
148-
"baseUrl": ".",
149-
"paths": {
150-
"react": ["sink.d.ts"]
151-
}
152-
}
153-
}
154-
```
155-
156-
`sink.d.ts:`
157-
158-
```ts
159-
declare module 'react';
160-
```
161-
162-
For more info see [this issue](https://github.com/sveltejs/language-tools/issues/228)

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

+18-5
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import {
44
CompletionList,
55
CompletionItemKind,
66
CompletionItem,
7+
InsertTextFormat,
78
} from 'vscode-languageserver';
89
import { SvelteTag, documentation, getLatestOpeningTag } from './SvelteTags';
910
import { isInTag } from '../../../lib/documents';
10-
1111
/**
1212
* Get completions for special svelte tags within moustache tags.
1313
*/
@@ -54,9 +54,18 @@ function getCompletionsWithRegardToTriggerCharacter(
5454

5555
if (triggerCharacter === '#') {
5656
return createCompletionItems([
57-
{ tag: 'if', label: 'if' },
58-
{ tag: 'each', label: 'each' },
59-
{ tag: 'await', label: 'await' },
57+
{ tag: 'if', label: 'if', insertText: 'if $1}\n\t$2\n{/if' },
58+
{ tag: 'each', label: 'each', insertText: 'each $1 as $2}\n\t$3\n{/each' },
59+
{
60+
tag: 'await',
61+
label: 'await :then',
62+
insertText: 'await $1}\n\t$2\n{:then $3} \n\t$4\n{/await',
63+
},
64+
{
65+
tag: 'await',
66+
label: 'await then',
67+
insertText: 'await $1 then $2}\n\t$3\n{/await',
68+
},
6069
]);
6170
}
6271

@@ -133,12 +142,16 @@ function showCompletionWithRegardsToOpenedTags(
133142
/**
134143
* Create the completion items for given labels and tags.
135144
*/
136-
function createCompletionItems(items: { label: string; tag: SvelteTag }[]): CompletionList {
145+
function createCompletionItems(
146+
items: { label: string; tag: SvelteTag; insertText?: string }[],
147+
): CompletionList {
137148
return CompletionList.create(
138149
// add sortText/preselect so it is ranked higher than other completions and selected first
139150
items.map(
140151
(item) =>
141152
<CompletionItem>{
153+
insertTextFormat: InsertTextFormat.Snippet,
154+
insertText: item.insertText,
142155
label: item.label,
143156
sortText: '-1',
144157
kind: CompletionItemKind.Keyword,

packages/language-server/src/plugins/typescript/DocumentSnapshot.ts

+8-14
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import {
1414
} from '../../lib/documents';
1515
import { pathToUrl } from '../../utils';
1616
import { ConsumerDocumentMapper } from './DocumentMapper';
17-
import { getScriptKindFromAttributes, getScriptKindFromFileName, isSvelteFilePath } from './utils';
17+
import {
18+
getScriptKindFromAttributes,
19+
getScriptKindFromFileName,
20+
isSvelteFilePath,
21+
getTsCheckComment,
22+
} from './utils';
1823

1924
/**
2025
* An error which occured while trying to parse/preprocess the svelte file contents.
@@ -120,10 +125,9 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions
120125
if (tsxMap) {
121126
tsxMap.sources = [document.uri];
122127

123-
const tsCheck = document.scriptInfo?.content.match(tsCheckRegex);
128+
const tsCheck = getTsCheckComment(document.scriptInfo?.content);
124129
if (tsCheck) {
125-
// second-last entry is the capturing group with the exact ts-check wording
126-
text = `//${tsCheck[tsCheck.length - 3]}${ts.sys.newLine}` + text;
130+
text = tsCheck + text;
127131
nrPrependedLines = 1;
128132
}
129133
}
@@ -330,13 +334,3 @@ export class SvelteSnapshotFragment implements SnapshotFragment {
330334
}
331335
}
332336
}
333-
334-
// The following regex matches @ts-check or @ts-nocheck if:
335-
// - it is before the first line of code (so other lines with comments before it are ok)
336-
// - must be @ts-(no)check
337-
// - the comment which has @ts-(no)check can have any type of whitespace before it, but not other characters
338-
// - what's coming after @ts-(no)check is irrelevant as long there is any kind of whitespace or line break, so this would be picked up, too: // @ts-check asdasd
339-
// [ \t\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]
340-
// is just \s (a.k.a any whitespace character) without linebreak and vertical tab
341-
// eslint-disable-next-line
342-
const tsCheckRegex = /^(\s*(\/\/[ \t\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\S]*)*\s*)*(\/\/[ \t\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*(@ts-(no)?check)($|\s))/;

packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ export class TypeScriptPlugin
7979
this.configManager = configManager;
8080
this.lsAndTsDocResolver = new LSAndTSDocResolver(docManager, workspacePath);
8181
this.completionProvider = new CompletionsProviderImpl(this.lsAndTsDocResolver);
82-
this.codeActionsProvider = new CodeActionsProviderImpl(this.lsAndTsDocResolver);
82+
this.codeActionsProvider = new CodeActionsProviderImpl(
83+
this.lsAndTsDocResolver,
84+
this.completionProvider,
85+
);
8386
this.updateImportsProvider = new UpdateImportsProviderImpl(this.lsAndTsDocResolver);
8487
this.diagnosticsProvider = new DiagnosticsProviderImpl(this.lsAndTsDocResolver);
8588
this.renameProvider = new RenameProviderImpl(this.lsAndTsDocResolver);
@@ -108,9 +111,10 @@ export class TypeScriptPlugin
108111
return null;
109112
}
110113
const declaration = ts.displayPartsToString(info.displayParts);
111-
const documentation = typeof info.documentation === 'string'
112-
? info.documentation
113-
: ts.displayPartsToString(info.documentation);
114+
const documentation =
115+
typeof info.documentation === 'string'
116+
? info.documentation
117+
: ts.displayPartsToString(info.documentation);
114118

115119
// https://microsoft.github.io/language-server-protocol/specification#textDocument_hover
116120
const contents = ['```typescript', declaration, '```']

packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import {
1111
import { Document, mapRangeToOriginal, isRangeInTag } from '../../../lib/documents';
1212
import { pathToUrl } from '../../../utils';
1313
import { CodeActionsProvider } from '../../interfaces';
14-
import { SnapshotFragment } from '../DocumentSnapshot';
14+
import { SnapshotFragment, SvelteSnapshotFragment } from '../DocumentSnapshot';
1515
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
1616
import { convertRange } from '../utils';
1717
import { flatten } from '../../../utils';
1818
import ts from 'typescript';
19+
import { CompletionsProviderImpl } from './CompletionProvider';
1920

2021
interface RefactorArgs {
2122
type: 'refactor';
@@ -25,7 +26,10 @@ interface RefactorArgs {
2526
}
2627

2728
export class CodeActionsProviderImpl implements CodeActionsProvider {
28-
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
29+
constructor(
30+
private readonly lsAndTsDocResolver: LSAndTSDocResolver,
31+
private readonly completionProvider: CompletionsProviderImpl,
32+
) {}
2933

3034
async getCodeActions(
3135
document: Document,
@@ -122,6 +126,17 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
122126
null,
123127
),
124128
change.textChanges.map((edit) => {
129+
if (
130+
fix.fixName === 'import' &&
131+
doc instanceof SvelteSnapshotFragment
132+
) {
133+
return this.completionProvider.codeActionChangeToTextEdit(
134+
document,
135+
doc,
136+
edit,
137+
true,
138+
);
139+
}
125140
return TextEdit.replace(
126141
mapRangeToOriginal(doc!, convertRange(doc!, edit.span)),
127142
edit.newText,

packages/language-server/src/plugins/typescript/features/CompletionProvider.ts

+7-14
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,9 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
210210

211211
for (const action of actions) {
212212
for (const change of action.changes) {
213-
edit.push(...this.codeActionChangesToTextEdit(
214-
document,
215-
fragment,
216-
change,
217-
isImport
218-
));
213+
edit.push(
214+
...this.codeActionChangesToTextEdit(document, fragment, change, isImport),
215+
);
219216
}
220217
}
221218

@@ -255,7 +252,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
255252
);
256253
}
257254

258-
private codeActionChangeToTextEdit(
255+
codeActionChangeToTextEdit(
259256
doc: Document,
260257
fragment: SvelteSnapshotFragment,
261258
change: ts.TextChange,
@@ -308,20 +305,16 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
308305
return TextEdit.replace(range, change.newText);
309306
}
310307

311-
private mapRangeForNewImport(
312-
fragment: SvelteSnapshotFragment,
313-
virtualRange: Range
314-
) {
308+
private mapRangeForNewImport(fragment: SvelteSnapshotFragment, virtualRange: Range) {
315309
const sourceMapableRange = this.offsetLinesAndMovetoStartOfLine(virtualRange, -1);
316-
const mappableRange = mapRangeToOriginal(
317-
fragment, sourceMapableRange);
310+
const mappableRange = mapRangeToOriginal(fragment, sourceMapableRange);
318311
return this.offsetLinesAndMovetoStartOfLine(mappableRange, 1);
319312
}
320313

321314
private offsetLinesAndMovetoStartOfLine({ start, end }: Range, offsetLines: number) {
322315
return Range.create(
323316
Position.create(start.line + offsetLines, 0),
324-
Position.create(end.line + offsetLines, 0)
317+
Position.create(end.line + offsetLines, 0),
325318
);
326319
}
327320

packages/language-server/src/plugins/typescript/features/RenameProvider.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export class RenameProviderImpl implements RenameProvider {
127127
if (
128128
!renameInfo.canRename ||
129129
renameInfo.kind === ts.ScriptElementKind.jsxAttribute ||
130-
renameInfo.fullDisplayName?.startsWith('JSX.')
130+
renameInfo.fullDisplayName?.includes("JSX.IntrinsicElements")
131131
) {
132132
return null;
133133
}

packages/language-server/src/plugins/typescript/service.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export function createLanguageService(
6666
const svelteModuleLoader = createSvelteModuleLoader(getSnapshot, compilerOptions);
6767

6868
const svelteTsPath = dirname(require.resolve('svelte2tsx'));
69-
const svelteTsxFiles = ['./svelte-shims.d.ts', './svelte-jsx.d.ts'].map((f) =>
69+
const svelteTsxFiles = ['./svelte-shims.d.ts', './svelte-jsx.d.ts', './svelte-native-jsx.d.ts'].map((f) =>
7070
ts.sys.resolvePath(resolve(svelteTsPath, f)),
7171
);
7272

@@ -162,8 +162,7 @@ export function createLanguageService(
162162
declaration: false,
163163
skipLibCheck: true,
164164
// these are needed to handle the results of svelte2tsx preprocessing:
165-
jsx: ts.JsxEmit.Preserve,
166-
jsxFactory: 'h',
165+
jsx: ts.JsxEmit.Preserve
167166
};
168167

169168
// always let ts parse config to get default compilerOption
@@ -203,6 +202,24 @@ export function createLanguageService(
203202
...forcedCompilerOptions,
204203
};
205204

205+
// detect which JSX namespace to use (svelte | svelteNative) if not specified or not compatible
206+
if (!compilerOptions.jsxFactory || !compilerOptions.jsxFactory.startsWith("svelte")) {
207+
//default to regular svelte, this causes the usage of the "svelte.JSX" namespace
208+
compilerOptions.jsxFactory = "svelte.createElement";
209+
210+
//override if we detect svelte-native
211+
if (workspacePath) {
212+
try {
213+
const svelteNativePkgInfo = getPackageInfo('svelte-native', workspacePath);
214+
if (svelteNativePkgInfo.path) {
215+
compilerOptions.jsxFactory = "svelteNative.createElement";
216+
}
217+
} catch (e) {
218+
//we stay regular svelte
219+
}
220+
}
221+
}
222+
206223
return { compilerOptions, files };
207224
}
208225

packages/language-server/src/plugins/typescript/utils.ts

+26
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,29 @@ export function mapSeverity(category: ts.DiagnosticCategory): DiagnosticSeverity
260260

261261
return DiagnosticSeverity.Error;
262262
}
263+
264+
// Matches comments that come before any non-comment content
265+
const commentsRegex = /^(\s*\/\/.*\s*)*/;
266+
// The following regex matches @ts-check or @ts-nocheck if:
267+
// - must be @ts-(no)check
268+
// - the comment which has @ts-(no)check can have any type of whitespace before it, but not other characters
269+
// - what's coming after @ts-(no)check is irrelevant as long there is any kind of whitespace or line break, so this would be picked up, too: // @ts-check asdasd
270+
// [ \t\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]
271+
// is just \s (a.k.a any whitespace character) without linebreak and vertical tab
272+
// eslint-disable-next-line max-len
273+
const tsCheckRegex = /\/\/[ \t\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*(@ts-(no)?check)($|\s)/;
274+
275+
/**
276+
* Returns `// @ts-check` or `// @ts-nocheck` if content starts with comments and has one of these
277+
* in its comments.
278+
*/
279+
export function getTsCheckComment(str = ''): string | undefined {
280+
const comments = str.match(commentsRegex)?.[0];
281+
if (comments) {
282+
const tsCheck = comments.match(tsCheckRegex);
283+
if (tsCheck) {
284+
// second-last entry is the capturing group with the exact ts-check wording
285+
return `// ${tsCheck[tsCheck.length - 3]}${ts.sys.newLine}`;
286+
}
287+
}
288+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('SveltePlugin#getCompletions', () => {
4949
});
5050

5151
it('should return completions for #', () => {
52-
expectCompletionsFor('{#').toEqual(['if', 'each', 'await']);
52+
expectCompletionsFor('{#').toEqual(['if', 'each', 'await :then', 'await then']);
5353
});
5454

5555
it('should return completions for @', () => {

packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ts from 'typescript';
66
import * as path from 'path';
77
import * as assert from 'assert';
88
import { Range, Position, CodeActionKind, TextDocumentEdit } from 'vscode-languageserver';
9+
import { CompletionsProviderImpl } from '../../../../src/plugins/typescript/features/CompletionProvider';
910

1011
const testDir = path.join(__dirname, '..');
1112

@@ -27,7 +28,8 @@ describe('CodeActionsProvider', () => {
2728
(textDocument) => new Document(textDocument.uri, textDocument.text),
2829
);
2930
const lsAndTsDocResolver = new LSAndTSDocResolver(docManager, testDir);
30-
const provider = new CodeActionsProviderImpl(lsAndTsDocResolver);
31+
const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver);
32+
const provider = new CodeActionsProviderImpl(lsAndTsDocResolver, completionProvider);
3133
const filePath = getFullPath(filename);
3234
const document = docManager.openDocument(<any>{
3335
uri: pathToUrl(filePath),

0 commit comments

Comments
 (0)