Skip to content

Commit fa0bfb9

Browse files
committed
Merge branch 'master' of https://github.com/sveltejs/language-tools into event-type-check
2 parents 8d7dc29 + 7f89480 commit fa0bfb9

File tree

88 files changed

+340
-151
lines changed

Some content is hidden

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

88 files changed

+340
-151
lines changed

packages/language-server/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"dependencies": {
4949
"cosmiconfig": "^6.0.0",
5050
"estree-walker": "^2.0.1",
51-
"lodash": "^4.17.10",
51+
"lodash": "^4.17.19",
5252
"magic-string": "^0.25.3",
5353
"prettier": "2.0.5",
5454
"prettier-plugin-svelte": "1.1.0",

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

+47-8
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,19 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
203203
}
204204

205205
const actions = detail?.codeActions;
206+
const isImport = !!detail?.source;
207+
206208
if (actions) {
207209
const edit: TextEdit[] = [];
208210

209211
for (const action of actions) {
210212
for (const change of action.changes) {
211-
edit.push(...this.codeActionChangesToTextEdit(document, fragment, change));
213+
edit.push(...this.codeActionChangesToTextEdit(
214+
document,
215+
fragment,
216+
change,
217+
isImport
218+
));
212219
}
213220
}
214221

@@ -241,16 +248,18 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
241248
doc: Document,
242249
fragment: SvelteSnapshotFragment,
243250
changes: ts.FileTextChanges,
251+
isImport: boolean,
244252
): TextEdit[] {
245253
return changes.textChanges.map((change) =>
246-
this.codeActionChangeToTextEdit(doc, fragment, change),
254+
this.codeActionChangeToTextEdit(doc, fragment, change, isImport),
247255
);
248256
}
249257

250258
private codeActionChangeToTextEdit(
251259
doc: Document,
252260
fragment: SvelteSnapshotFragment,
253261
change: ts.TextChange,
262+
isImport: boolean,
254263
): TextEdit {
255264
change.newText = this.changeSvelteComponentName(change.newText);
256265

@@ -264,28 +273,58 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
264273
}
265274

266275
const { span } = change;
267-
// prevent newText from being placed like this: <script>import {} from ''
268-
if (span.start === 0) {
269-
change.newText = ts.sys.newLine + change.newText;
276+
277+
const virutalRange = convertRange(fragment, span);
278+
let range: Range;
279+
const isNewImport = isImport && virutalRange.start.character === 0;
280+
281+
// Since new import always can't be mapped, we'll have special treatment here
282+
// but only hack this when there is multiple line in script
283+
if (isNewImport && virutalRange.start.line > 1) {
284+
range = this.mapRangeForNewImport(fragment, virutalRange);
285+
} else {
286+
range = mapRangeToOriginal(fragment, virutalRange);
270287
}
271288

272-
let range = mapRangeToOriginal(fragment, convertRange(fragment, span));
273-
// If range is somehow not mapped in parent or the import is mapped wrong,
289+
// If range is somehow not mapped in parent,
290+
// the import is mapped wrong or is outside script tag,
274291
// use script starting point instead.
275292
// This happens among other things if the completion is the first import of the file.
276293
if (
277294
range.start.line === -1 ||
278-
(range.start.line === 0 && range.start.character <= 1 && span.length === 0)
295+
(range.start.line === 0 && range.start.character <= 1 && span.length === 0) ||
296+
doc.offsetAt(range.start) > scriptTagInfo.end
279297
) {
280298
range = convertRange(doc, {
281299
start: scriptTagInfo.start,
282300
length: span.length,
283301
});
284302
}
303+
// prevent newText from being placed like this: <script>import {} from ''
304+
if (range.start.line === 0) {
305+
change.newText = ts.sys.newLine + change.newText;
306+
}
285307

286308
return TextEdit.replace(range, change.newText);
287309
}
288310

311+
private mapRangeForNewImport(
312+
fragment: SvelteSnapshotFragment,
313+
virtualRange: Range
314+
) {
315+
const sourceMapableRange = this.offsetLinesAndMovetoStartOfLine(virtualRange, -1);
316+
const mappableRange = mapRangeToOriginal(
317+
fragment, sourceMapableRange);
318+
return this.offsetLinesAndMovetoStartOfLine(mappableRange, 1);
319+
}
320+
321+
private offsetLinesAndMovetoStartOfLine({ start, end }: Range, offsetLines: number) {
322+
return Range.create(
323+
Position.create(start.line + offsetLines, 0),
324+
Position.create(end.line + offsetLines, 0)
325+
);
326+
}
327+
289328
private isSvelteComponentImport(className: string) {
290329
return className.endsWith('__SvelteComponent_');
291330
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export class RenameProviderImpl implements RenameProvider {
152152
// First find out if it's really the "rename prop inside component with that prop" case
153153
// Use original document for that because only there the `export` is present.
154154
const regex = new RegExp(
155-
`export\\s+(const|let)\\s+${this.getVariableAtPosition(
155+
`export\\s+let\\s+${this.getVariableAtPosition(
156156
tsDoc,
157157
fragment,
158158
lang,

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ describe('CodeActionsProvider', () => {
191191
},
192192
},
193193
textRange: {
194-
pos: 129,
195-
end: 163,
194+
pos: 130,
195+
end: 164,
196196
},
197197
},
198198
],
@@ -281,8 +281,8 @@ describe('CodeActionsProvider', () => {
281281
},
282282
},
283283
textRange: {
284-
pos: 129,
285-
end: 163,
284+
pos: 130,
285+
end: 164,
286286
},
287287
},
288288
],

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ describe('CompletionProviderImpl', () => {
258258

259259
assert.deepEqual(
260260
additionalTextEdits![0]?.range,
261-
Range.create(Position.create(0, 8), Position.create(0, 8)),
261+
Range.create(Position.create(2, 0), Position.create(2, 0)),
262262
);
263263
});
264264

@@ -285,7 +285,7 @@ describe('CompletionProviderImpl', () => {
285285

286286
assert.strictEqual(
287287
harmonizeNewLines(additionalTextEdits![0]?.newText),
288-
`import { blubb } from './definitions';${newLine}`,
288+
`${newLine}import { blubb } from './definitions';${newLine}`,
289289
);
290290

291291
assert.deepEqual(

packages/svelte-vscode/syntaxes/svelte.tmLanguage.json

+28-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"scopeName": "source.svelte",
44
"fileTypes": ["svelte"],
55
"uuid": "7582b62f-51d9-4a84-8c8d-fc189530faf6",
6+
67
"patterns": [
78
{
89
"begin": "(<)(style)\\b(?=[^>]*(?:type=('text/sass'|\"text/sass\")|lang=(sass|'sass'|\"sass\")))(?![^/>]*/>\\s*$)",
@@ -540,7 +541,7 @@
540541
]
541542
},
542543
{
543-
"begin": "(</?)([a-zA-Z][a-zA-Z0-9:-]*)",
544+
"begin": "(</?)([a-z][a-zA-Z0-9:-]*)",
544545
"beginCaptures": {
545546
"1": {
546547
"name": "punctuation.definition.tag.begin.html"
@@ -562,6 +563,32 @@
562563
}
563564
]
564565
},
566+
{
567+
"begin": "(</?)([A-Z][a-zA-Z0-9:-]*)",
568+
"beginCaptures": {
569+
"1": {
570+
"name": "punctuation.definition.tag.begin.html"
571+
},
572+
"2": {
573+
"name": "support.class.component.svelte"
574+
}
575+
},
576+
"end": "(/?>)",
577+
"endCaptures": {
578+
"1": {
579+
"name": "punctuation.definition.tag.end.html"
580+
},
581+
"2": {
582+
"name": "support.class.component.svelte"
583+
}
584+
},
585+
"name": "meta.tag.svelte",
586+
"patterns": [
587+
{
588+
"include": "#tag-stuff"
589+
}
590+
]
591+
},
565592
{
566593
"begin": "<!--",
567594
"end": "-->",

packages/svelte2tsx/src/htmlxtojsx.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,12 @@ export function convertHtmlxToJsx(
9393
};
9494

9595
const handleClassDirective = (attr: Node) => {
96-
const needCurly = attr.expression.start == attr.start + 'class:'.length;
9796
str.overwrite(attr.start, attr.expression.start, `{...__sveltets_ensureType(Boolean, !!(`);
98-
str.appendLeft(attr.expression.end, `))${needCurly ? '}' : ''}`);
99-
if (htmlx[attr.end - 1] == '"') {
100-
str.remove(attr.end - 1, attr.end);
97+
const endBrackets = `))}`;
98+
if (attr.end !== attr.expression.end) {
99+
str.overwrite(attr.expression.end, attr.end, endBrackets);
100+
} else {
101+
str.appendLeft(attr.end, endBrackets);
101102
}
102103
};
103104

@@ -511,7 +512,6 @@ export function convertHtmlxToJsx(
511512
// {() => {let _$$p = (somePromise);
512513
const handleAwait = (awaitBlock: Node) => {
513514
str.overwrite(awaitBlock.start, awaitBlock.expression.start, '{() => {let _$$p = (');
514-
str.prependLeft(awaitBlock.expression.end, ');');
515515
// then value } | {:then value} ->
516516
// _$$p.then((value) => {<>
517517
let thenStart: number;
@@ -527,11 +527,16 @@ export function convertHtmlxToJsx(
527527
str.prependLeft(thenStart, '</>; ');
528528
// add the start tag too
529529
const awaitEnd = htmlx.indexOf('}', awaitBlock.expression.end);
530-
str.remove(awaitEnd, awaitEnd + 1);
531-
str.appendRight(awaitEnd, ' <>');
530+
531+
// somePromise} -> somePromise);
532+
str.overwrite(awaitBlock.expression.end, awaitEnd + 1, ');');
533+
str.appendRight(awaitEnd + 1, ' <>');
532534
} else {
533535
thenEnd = htmlx.lastIndexOf('}', awaitBlock.then.start) + 1;
534536
thenStart = htmlx.indexOf('then', awaitBlock.expression.end);
537+
538+
// somePromise then -> somePromise); then
539+
str.overwrite(awaitBlock.expression.end, thenStart, '); ');
535540
}
536541
if (awaitBlock.value) {
537542
str.overwrite(

packages/svelte2tsx/src/interfaces.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import MagicString from 'magic-string';
2+
import { Node } from 'estree-walker';
3+
4+
export type ExportedNames = Map<
5+
string,
6+
{
7+
type?: string;
8+
identifierText?: string;
9+
required?: boolean;
10+
}
11+
>;
12+
13+
export interface InstanceScriptProcessResult {
14+
exportedNames: ExportedNames;
15+
uses$$props: boolean;
16+
uses$$restProps: boolean;
17+
getters: Set<string>;
18+
events: Map<string, string | string[]>;
19+
}
20+
21+
export interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
22+
str: MagicString;
23+
scriptTag: Node;
24+
scriptDestination: number;
25+
slots: Map<string, Map<string, string>>;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const createClassGetter = (name: string) =>
2+
`\n${' '.repeat(4)}get ${name}() { return render().getters.${name} }`;
3+
export const createClassGetters = (names: Set<string>) => {
4+
return Array.from(names).map(createClassGetter).join('');
5+
};
6+
export function createRenderFunctionGetterStr(getters: Set<string>) {
7+
const properties = Array.from(getters).map((name) => `${name}: ${name}`);
8+
return `{${properties.join(', ')}}`;
9+
}

0 commit comments

Comments
 (0)