Skip to content

Commit 1452631

Browse files
(feat) Support for multiple preprocessors (#384)
* (feat) Support for multiple preprocessors #279 Co-authored-by: Christian Kaisermann <[email protected]>
1 parent b089bb2 commit 1452631

File tree

5 files changed

+136
-74
lines changed

5 files changed

+136
-74
lines changed

packages/language-server/src/lib/documents/Document.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ export class Document extends WritableDocument {
6666
return null;
6767
}
6868

69-
const defaultLang = this.config.preprocess?.defaultLanguages?.[tag];
69+
const defaultLang = Array.isArray(this.config.preprocess)
70+
? this.config.preprocess.find((group) => group.defaultLanguages?.[tag])
71+
?.defaultLanguages?.[tag]
72+
: this.config.preprocess?.defaultLanguages?.[tag];
73+
7074
if (!tagInfo.attributes.lang && !tagInfo.attributes.type && defaultLang) {
7175
tagInfo.attributes.lang = defaultLang;
7276
}

packages/language-server/src/lib/documents/DocumentMapper.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,39 @@ export interface DocumentMapper {
5151
* Does not map, returns positions as is.
5252
*/
5353
export class IdentityMapper implements DocumentMapper {
54-
constructor(private url: string) {}
54+
constructor(private url: string, private parent?: DocumentMapper) {}
5555

5656
getOriginalPosition(generatedPosition: Position): Position {
57+
if (this.parent) {
58+
generatedPosition = this.getOriginalPosition(generatedPosition);
59+
}
60+
5761
return generatedPosition;
5862
}
5963

6064
getGeneratedPosition(originalPosition: Position): Position {
65+
if (this.parent) {
66+
originalPosition = this.getGeneratedPosition(originalPosition);
67+
}
68+
6169
return originalPosition;
6270
}
6371

64-
isInGenerated(): boolean {
72+
isInGenerated(position: Position): boolean {
73+
if (this.parent && !this.parent.isInGenerated(position)) {
74+
return false;
75+
}
76+
6577
return true;
6678
}
6779

6880
getURL(): string {
6981
return this.url;
7082
}
83+
84+
destroy() {
85+
this.parent?.destroy?.();
86+
}
7187
}
7288

7389
/**
@@ -105,9 +121,17 @@ export class FragmentMapper implements DocumentMapper {
105121
}
106122

107123
export class SourceMapDocumentMapper implements DocumentMapper {
108-
constructor(protected consumer: SourceMapConsumer, protected sourceUri: string) {}
124+
constructor(
125+
protected consumer: SourceMapConsumer,
126+
protected sourceUri: string,
127+
private parent?: DocumentMapper,
128+
) {}
109129

110130
getOriginalPosition(generatedPosition: Position): Position {
131+
if (this.parent) {
132+
generatedPosition = this.parent.getOriginalPosition(generatedPosition);
133+
}
134+
111135
const mapped = this.consumer.originalPositionFor({
112136
line: generatedPosition.line + 1,
113137
column: generatedPosition.character,
@@ -128,6 +152,10 @@ export class SourceMapDocumentMapper implements DocumentMapper {
128152
}
129153

130154
getGeneratedPosition(originalPosition: Position): Position {
155+
if (this.parent) {
156+
originalPosition = this.parent.getGeneratedPosition(originalPosition);
157+
}
158+
131159
const mapped = this.consumer.generatedPositionFor({
132160
line: originalPosition.line + 1,
133161
column: originalPosition.character,
@@ -151,6 +179,10 @@ export class SourceMapDocumentMapper implements DocumentMapper {
151179
}
152180

153181
isInGenerated(position: Position): boolean {
182+
if (this.parent && !this.isInGenerated(position)) {
183+
return false;
184+
}
185+
154186
const generated = this.getGeneratedPosition(position);
155187
return generated.line >= 0;
156188
}
@@ -163,6 +195,7 @@ export class SourceMapDocumentMapper implements DocumentMapper {
163195
* Needs to be called when source mapper is no longer needed in order to prevent memory leaks.
164196
*/
165197
destroy() {
198+
this.parent?.destroy?.();
166199
this.consumer.destroy();
167200
}
168201
}

packages/language-server/src/lib/documents/configLoader.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@ import { CompileOptions } from 'svelte/types/compiler/interfaces';
44
import { PreprocessorGroup } from 'svelte/types/compiler/preprocess';
55
import { importSveltePreprocess } from '../../importPackage';
66

7+
export type InternalPreprocessorGroup = PreprocessorGroup & {
8+
/**
9+
* svelte-preprocess has this since 4.x
10+
*/
11+
defaultLanguages?: {
12+
markup?: string;
13+
script?: string;
14+
style?: string;
15+
};
16+
};
17+
718
export interface SvelteConfig {
819
compilerOptions?: CompileOptions;
9-
preprocess?: PreprocessorGroup & {
10-
/**
11-
* svelte-preprocess has this since 4.x
12-
*/
13-
defaultLanguages?: { markup?: string; script?: string; style?: string };
14-
};
20+
preprocess?: InternalPreprocessorGroup | InternalPreprocessorGroup[];
1521
loadConfigError?: any;
1622
}
1723

packages/language-server/src/plugins/svelte/SvelteDocument.ts

Lines changed: 80 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
TagInformation,
1717
} from '../../lib/documents';
1818
import { importSvelte } from '../../importPackage';
19+
import { isNotNullOrUndefined } from '../../utils';
1920

2021
export type SvelteCompileResult = ReturnType<typeof compile>;
2122

@@ -93,41 +94,44 @@ export class SvelteDocument {
9394
}
9495

9596
export class TranspiledSvelteDocument implements Pick<DocumentMapper, 'getOriginalPosition'> {
96-
static async create(document: Document, preprocessors: PreprocessorGroup = {}) {
97-
const { transpiled, processedScript, processedStyle } = await transpile(
97+
static async create(
98+
document: Document,
99+
preprocessors: PreprocessorGroup | PreprocessorGroup[] = [],
100+
) {
101+
const { transpiled, processedScripts, processedStyles } = await transpile(
98102
document,
99103
preprocessors,
100104
);
101105
const scriptMapper = await SvelteFragmentMapper.createScript(
102106
document,
103107
transpiled,
104-
processedScript,
108+
processedScripts,
105109
);
106110
const styleMapper = await SvelteFragmentMapper.createStyle(
107111
document,
108112
transpiled,
109-
processedStyle,
113+
processedStyles,
110114
);
111115

112116
return new TranspiledSvelteDocument(document, transpiled, scriptMapper, styleMapper);
113117
}
114118

115-
private fragmentInfos = [this.scriptMapper.fragmentInfo, this.styleMapper.fragmentInfo].sort(
116-
(i1, i2) => i1.end - i2.end,
117-
);
119+
private fragmentInfos = [this.scriptMapper?.fragmentInfo, this.styleMapper?.fragmentInfo]
120+
.filter(isNotNullOrUndefined)
121+
.sort((i1, i2) => i1.end - i2.end);
118122

119123
private constructor(
120124
private parent: Document,
121125
private transpiled: string,
122-
public scriptMapper: SvelteFragmentMapper,
123-
public styleMapper: SvelteFragmentMapper,
126+
public scriptMapper: SvelteFragmentMapper | null,
127+
public styleMapper: SvelteFragmentMapper | null,
124128
) {}
125129

126130
getOriginalPosition(generatedPosition: Position): Position {
127-
if (this.scriptMapper.isInTranspiledFragment(generatedPosition)) {
131+
if (this.scriptMapper?.isInTranspiledFragment(generatedPosition)) {
128132
return this.scriptMapper.getOriginalPosition(generatedPosition);
129133
}
130-
if (this.styleMapper.isInTranspiledFragment(generatedPosition)) {
134+
if (this.styleMapper?.isInTranspiledFragment(generatedPosition)) {
131135
return this.styleMapper.getOriginalPosition(generatedPosition);
132136
}
133137

@@ -154,13 +158,13 @@ export class TranspiledSvelteDocument implements Pick<DocumentMapper, 'getOrigin
154158
* Needs to be called before cleanup to prevent source map memory leaks.
155159
*/
156160
destroy() {
157-
this.scriptMapper.destroy();
158-
this.styleMapper.destroy();
161+
this.scriptMapper?.destroy();
162+
this.styleMapper?.destroy();
159163
}
160164
}
161165

162166
export class SvelteFragmentMapper {
163-
static async createStyle(originalDoc: Document, transpiled: string, processed?: Processed) {
167+
static async createStyle(originalDoc: Document, transpiled: string, processed: Processed[]) {
164168
return SvelteFragmentMapper.create(
165169
originalDoc,
166170
transpiled,
@@ -170,7 +174,7 @@ export class SvelteFragmentMapper {
170174
);
171175
}
172176

173-
static async createScript(originalDoc: Document, transpiled: string, processed?: Processed) {
177+
static async createScript(originalDoc: Document, transpiled: string, processed: Processed[]) {
174178
return SvelteFragmentMapper.create(
175179
originalDoc,
176180
transpiled,
@@ -185,14 +189,12 @@ export class SvelteFragmentMapper {
185189
transpiled: string,
186190
originalTagInfo: TagInformation | null,
187191
transpiledTagInfo: TagInformation | null,
188-
processed?: Processed,
192+
processed: Processed[],
189193
) {
190-
const sourceMapper = processed?.map
191-
? new SourceMapDocumentMapper(
192-
await new SourceMapConsumer(processed.map.toString()),
193-
originalDoc.uri,
194-
)
195-
: new IdentityMapper(originalDoc.uri);
194+
const sourceMapper =
195+
processed.length > 0
196+
? await SvelteFragmentMapper.createSourceMapper(processed, originalDoc)
197+
: new IdentityMapper(originalDoc.uri);
196198

197199
if (originalTagInfo && transpiledTagInfo) {
198200
const sourceLength = originalTagInfo.container.end - originalTagInfo.container.start;
@@ -208,11 +210,20 @@ export class SvelteFragmentMapper {
208210
);
209211
}
210212

211-
return new SvelteFragmentMapper(
212-
{ end: -1, diff: 0 },
213-
new IdentityMapper(originalDoc.uri),
214-
new IdentityMapper(originalDoc.uri),
215-
sourceMapper,
213+
return null;
214+
}
215+
216+
private static async createSourceMapper(processed: Processed[], originalDoc: Document) {
217+
return processed.reduce(
218+
async (parent, processedSingle) =>
219+
processedSingle?.map
220+
? new SourceMapDocumentMapper(
221+
await new SourceMapConsumer(processedSingle.map.toString()),
222+
originalDoc.uri,
223+
await parent,
224+
)
225+
: new IdentityMapper(originalDoc.uri, await parent),
226+
Promise.resolve<DocumentMapper>(<any>undefined),
216227
);
217228
}
218229

@@ -262,49 +273,56 @@ export class SvelteFragmentMapper {
262273
}
263274
}
264275

265-
async function transpile(document: Document, preprocessors: PreprocessorGroup = {}) {
266-
const preprocessor: PreprocessorGroup = {};
267-
let processedScript: Processed | undefined;
268-
let processedStyle: Processed | undefined;
269-
270-
preprocessor.markup = preprocessors.markup;
271-
272-
if (preprocessors.script) {
273-
preprocessor.script = async (args: any) => {
274-
try {
275-
const res = await preprocessors.script!(args);
276-
if (res && res.map) {
277-
processedScript = res;
276+
async function transpile(
277+
document: Document,
278+
preprocessors: PreprocessorGroup | PreprocessorGroup[] = [],
279+
) {
280+
preprocessors = Array.isArray(preprocessors) ? preprocessors : [preprocessors];
281+
const processedScripts: Processed[] = [];
282+
const processedStyles: Processed[] = [];
283+
284+
const wrappedPreprocessors = preprocessors.map((preprocessor) => {
285+
const wrappedPreprocessor: PreprocessorGroup = { markup: preprocessor.markup };
286+
287+
if (preprocessor.script) {
288+
wrappedPreprocessor.script = async (args: any) => {
289+
try {
290+
const res = await preprocessor.script!(args);
291+
if (res && res.map) {
292+
processedScripts.push(res);
293+
}
294+
return res;
295+
} catch (e) {
296+
e.__source = TranspileErrorSource.Script;
297+
throw e;
278298
}
279-
return res;
280-
} catch (e) {
281-
e.__source = TranspileErrorSource.Script;
282-
throw e;
283-
}
284-
};
285-
}
299+
};
300+
}
286301

287-
if (preprocessors.style) {
288-
preprocessor.style = async (args: any) => {
289-
try {
290-
const res = await preprocessors.style!(args);
291-
if (res && res.map) {
292-
processedStyle = res;
302+
if (preprocessor.style) {
303+
wrappedPreprocessor.style = async (args: any) => {
304+
try {
305+
const res = await preprocessor.style!(args);
306+
if (res && res.map) {
307+
processedStyles.push(res);
308+
}
309+
return res;
310+
} catch (e) {
311+
e.__source = TranspileErrorSource.Style;
312+
throw e;
293313
}
294-
return res;
295-
} catch (e) {
296-
e.__source = TranspileErrorSource.Style;
297-
throw e;
298-
}
299-
};
300-
}
314+
};
315+
}
316+
317+
return wrappedPreprocessor;
318+
});
301319

302320
const svelte = importSvelte(document.getFilePath() || '');
303321
const transpiled = (
304-
await svelte.preprocess(document.getText(), preprocessor, {
322+
await svelte.preprocess(document.getText(), wrappedPreprocessors, {
305323
filename: document.getFilePath() || '',
306324
})
307325
).toString();
308326

309-
return { transpiled, processedScript, processedStyle };
327+
return { transpiled, processedScripts, processedStyles };
310328
}

packages/language-server/test/plugins/svelte/SvelteDocument.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ describe('Svelte Document', () => {
5050
// stub svelte preprocess and getOriginalPosition
5151
// to fake a source mapping process
5252
sinon.stub(importPackage, 'importSvelte').returns({
53-
preprocess: (text, preprocessor: any) => {
54-
preprocessor.script();
53+
preprocess: (text, preprocessor) => {
54+
preprocessor = Array.isArray(preprocessor) ? preprocessor : [preprocessor];
55+
preprocessor.forEach((p) => p.script?.(<any>{}));
5556
return Promise.resolve({
5657
code: getSourceCode(true),
5758
dependencies: [],

0 commit comments

Comments
 (0)