Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) support default language of svelte-preprocess #291

Merged
merged 2 commits into from
Jul 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { dirname, resolve } from 'path';
import * as prettier from 'prettier';
import * as svelte from 'svelte/compiler';
import sveltePreprocess from 'svelte-preprocess';
import { Logger } from '../logger';
import { Logger } from './logger';

export function getPackageInfo(packageName: string, fromPath: string) {
const packageJSONPath = require.resolve(`${packageName}/package.json`, {
Expand Down
35 changes: 28 additions & 7 deletions packages/language-server/src/lib/documents/Document.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { urlToPath } from '../../utils';
import { WritableDocument } from './DocumentBase';
import { TagInformation, extractStyleTag, extractScriptTags } from './utils';
import { extractScriptTags, extractStyleTag, TagInformation } from './utils';
import { SvelteConfig, loadConfig } from './configLoader';

/**
* Represents a text document contains a svelte component.
Expand All @@ -10,17 +11,21 @@ export class Document extends WritableDocument {
scriptInfo: TagInformation | null = null;
moduleScriptInfo: TagInformation | null = null;
styleInfo: TagInformation | null = null;
config!: SvelteConfig;

constructor(public url: string, public content: string) {
super();
this.updateTagInfo();
this.updateDocInfo();
}

private updateTagInfo() {
private updateDocInfo() {
if (!this.config || this.config.loadConfigError) {
this.config = loadConfig(this.getFilePath() || '');
}
const scriptTags = extractScriptTags(this.content);
this.scriptInfo = scriptTags?.script || null;
this.moduleScriptInfo = scriptTags?.moduleScript || null;
this.styleInfo = extractStyleTag(this.content);
this.scriptInfo = this.addDefaultLanguage(scriptTags?.script || null, 'script');
this.moduleScriptInfo = this.addDefaultLanguage(scriptTags?.moduleScript || null, 'script');
this.styleInfo = this.addDefaultLanguage(extractStyleTag(this.content), 'style');
}

/**
Expand All @@ -36,7 +41,7 @@ export class Document extends WritableDocument {
setText(text: string) {
this.content = text;
this.version++;
this.updateTagInfo();
this.updateDocInfo();
}

/**
Expand All @@ -52,4 +57,20 @@ export class Document extends WritableDocument {
getURL() {
return this.url;
}

private addDefaultLanguage(
tagInfo: TagInformation | null,
tag: 'style' | 'script',
): TagInformation | null {
if (!tagInfo) {
return null;
}

const defaultLang = this.config.preprocess?.defaultLanguages?.[tag];
if (!tagInfo.attributes.lang && !tagInfo.attributes.type && defaultLang) {
tagInfo.attributes.lang = defaultLang;
}

return tagInfo;
}
}
74 changes: 74 additions & 0 deletions packages/language-server/src/lib/documents/configLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Logger } from '../../logger';
import { cosmiconfigSync } from 'cosmiconfig';
import { CompileOptions } from 'svelte/types/compiler/interfaces';
import { PreprocessorGroup } from 'svelte/types/compiler/preprocess';
import { importSveltePreprocess } from '../../importPackage';

export interface SvelteConfig {
compilerOptions?: CompileOptions;
preprocess?: PreprocessorGroup & {
/**
* svelte-preprocess has this since 4.x
*/
defaultLanguages?: { markup?: string; script?: string; style?: string };
};
loadConfigError?: any;
}

const DEFAULT_OPTIONS: CompileOptions = {
dev: true,
};

const NO_GENERATE: CompileOptions = {
generate: false,
};

const svelteConfigExplorer = cosmiconfigSync('svelte', {
packageProp: 'svelte-ls',
cache: true,
});

/**
* Tries to load `svelte.config.js`
*
* @param path File path of the document to load the config for
*/
export function loadConfig(path: string): SvelteConfig {
Logger.log('Trying to load config for', path);
try {
const result = svelteConfigExplorer.search(path);
const config: SvelteConfig = result?.config ?? useFallbackPreprocessor(path, false);
if (result) {
Logger.log('Found config at ', result.filepath);
}
return {
...config,
compilerOptions: { ...DEFAULT_OPTIONS, ...config.compilerOptions, ...NO_GENERATE },
};
} catch (err) {
Logger.error('Error while loading config');
Logger.error(err);
return {
...useFallbackPreprocessor(path, true),
compilerOptions: {
...DEFAULT_OPTIONS,
...NO_GENERATE,
},
loadConfigError: err,
};
}
}

function useFallbackPreprocessor(path: string, foundConfig: boolean): SvelteConfig {
Logger.log(
(foundConfig
? 'Found svelte.config.js but there was an error loading it. '
: 'No svelte.config.js found. ') +
'Using https://github.com/sveltejs/svelte-preprocess as fallback',
);
return {
preprocess: importSveltePreprocess(path)({
typescript: { transpileOnly: true },
}),
};
}
25 changes: 10 additions & 15 deletions packages/language-server/src/plugins/svelte/SvelteDocument.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
import { SourceMapConsumer } from 'source-map';
import { PreprocessorGroup } from 'svelte-preprocess/dist/types';
import { PreprocessorGroup } from 'svelte/types/compiler/preprocess';
import type { compile } from 'svelte/compiler';
import { CompileOptions } from 'svelte/types/compiler/interfaces';
import { Processed } from 'svelte/types/compiler/preprocess';
import { Position } from 'vscode-languageserver';
import {
Document,
DocumentMapper,
extractScriptTags,
extractStyleTag,
FragmentMapper,
IdentityMapper,
offsetAt,
SourceMapDocumentMapper,
TagInformation,
offsetAt,
extractStyleTag,
extractScriptTags,
} from '../../lib/documents';
import { importSvelte } from '../importPackage';
import { CompileOptions } from 'svelte/types/compiler/interfaces';
import { importSvelte } from '../../importPackage';

export type SvelteCompileResult = ReturnType<typeof compile>;

export interface SvelteConfig {
compilerOptions?: CompileOptions;
preprocess?: PreprocessorGroup;
loadConfigError?: any;
}

export enum TranspileErrorSource {
Script = 'Script',
Style = 'Style',
Expand All @@ -43,8 +37,9 @@ export class SvelteDocument {
public languageId = 'svelte';
public version = 0;
public uri = this.parent.uri;
public config = this.parent.config;

constructor(private parent: Document, public config: SvelteConfig) {
constructor(private parent: Document) {
this.script = this.parent.scriptInfo;
this.moduleScript = this.parent.moduleScriptInfo;
this.style = this.parent.styleInfo;
Expand All @@ -67,15 +62,15 @@ export class SvelteDocument {
if (!this.transpiledDoc) {
this.transpiledDoc = await TranspiledSvelteDocument.create(
this.parent,
this.config.preprocess,
this.parent.config.preprocess,
);
}
return this.transpiledDoc;
}

async getCompiled(): Promise<SvelteCompileResult> {
if (!this.compileResult) {
this.compileResult = await this.getCompiledWith(this.config.compilerOptions);
this.compileResult = await this.getCompiledWith(this.parent.config.compilerOptions);
}

return this.compileResult;
Expand Down
68 changes: 4 additions & 64 deletions packages/language-server/src/plugins/svelte/SveltePlugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { cosmiconfig } from 'cosmiconfig';
import { CompileOptions } from 'svelte/types/compiler/interfaces';
import {
CodeAction,
CodeActionContext,
Expand All @@ -12,29 +11,21 @@ import {
WorkspaceEdit,
} from 'vscode-languageserver';
import { Document } from '../../lib/documents';
import { Logger } from '../../logger';
import { LSConfigManager, LSSvelteConfig } from '../../ls-config';
import { importPrettier, importSveltePreprocess } from '../importPackage';
import { importPrettier } from '../../importPackage';
import {
CodeActionsProvider,
CompletionsProvider,
DiagnosticsProvider,
FormattingProvider,
HoverProvider,
} from '../interfaces';
import { getCodeActions, executeCommand } from './features/getCodeActions';
import { executeCommand, getCodeActions } from './features/getCodeActions';
import { getCompletions } from './features/getCompletions';
import { getDiagnostics } from './features/getDiagnostics';
import { getHoverInfo } from './features/getHoverInfo';
import { SvelteCompileResult, SvelteConfig, SvelteDocument } from './SvelteDocument';
import { SvelteCompileResult, SvelteDocument } from './SvelteDocument';

const DEFAULT_OPTIONS: CompileOptions = {
dev: true,
};

const NO_GENERATE: CompileOptions = {
generate: false,
};
export class SveltePlugin
implements
DiagnosticsProvider,
Expand Down Expand Up @@ -152,60 +143,9 @@ export class SveltePlugin
let svelteDoc = this.docManager.get(document);
if (!svelteDoc || svelteDoc.version !== document.version) {
svelteDoc?.destroyTranspiled();
// Reuse previous config. Assumption: Config does not change often (if at all).
const config =
svelteDoc?.config && !svelteDoc.config.loadConfigError
? svelteDoc.config
: await this.loadConfig(document);
svelteDoc = new SvelteDocument(document, config);
svelteDoc = new SvelteDocument(document);
this.docManager.set(document, svelteDoc);
}
return svelteDoc;
}

private async loadConfig(document: Document): Promise<SvelteConfig> {
Logger.log('Trying to load config for', document.getFilePath());
try {
const result = await this.cosmiConfigExplorer.search(document.getFilePath() || '');
const config: SvelteConfig =
result?.config ?? this.useFallbackPreprocessor(document, false);
if (result) {
Logger.log('Found config at ', result.filepath);
}
return {
...config,
compilerOptions: { ...DEFAULT_OPTIONS, ...config.compilerOptions, ...NO_GENERATE },
};
} catch (err) {
Logger.error('Error while loading config');
Logger.error(err);
return {
...this.useFallbackPreprocessor(document, true),
compilerOptions: {
...DEFAULT_OPTIONS,
...NO_GENERATE,
},
loadConfigError: err,
};
}
}

private useFallbackPreprocessor(document: Document, foundConfig: boolean): SvelteConfig {
const needsConfig =
document.styleInfo?.attributes.lang ||
document.styleInfo?.attributes.type ||
document.scriptInfo?.attributes.lang ||
document.scriptInfo?.attributes.type;
Logger.log(
(foundConfig
? 'Found svelte.config.js but there was an error loading it. '
: 'No svelte.config.js found' + (needsConfig ? ' but one is needed. ' : '. ')) +
'Using https://github.com/sveltejs/svelte-preprocess as fallback',
);
return {
preprocess: importSveltePreprocess(document.getFilePath() || '')({
typescript: { transpileOnly: true },
}),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
positionAt,
TagInformation,
isInTag,
extractScriptTags,
} from '../../lib/documents';
import { pathToUrl } from '../../utils';
import { ConsumerDocumentMapper } from './DocumentMapper';
Expand Down Expand Up @@ -112,13 +111,10 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions
let text = document.getText();

try {
const tsx = svelte2tsx(
text,
{
strictMode: options.strictMode,
filename: document.getFilePath() ?? undefined,
}
);
const tsx = svelte2tsx(text, {
strictMode: options.strictMode,
filename: document.getFilePath() ?? undefined,
});
text = tsx.code;
tsxMap = tsx.map;
if (tsxMap) {
Expand Down Expand Up @@ -175,10 +171,11 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot {

get scriptKind() {
if (!this._scriptKind) {
const scriptTags = extractScriptTags(this.parent.getText());
const scriptKind = getScriptKindFromAttributes(scriptTags?.script?.attributes ?? {});
const scriptKind = getScriptKindFromAttributes(
this.parent.scriptInfo?.attributes ?? {},
);
const moduleScriptKind = getScriptKindFromAttributes(
scriptTags?.moduleScript?.attributes ?? {},
this.parent.moduleScriptInfo?.attributes ?? {},
);
this._scriptKind = [scriptKind, moduleScriptKind].includes(ts.ScriptKind.TSX)
? ts.ScriptKind.TSX
Expand Down
2 changes: 1 addition & 1 deletion packages/language-server/src/plugins/typescript/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { dirname, resolve } from 'path';
import ts from 'typescript';
import { Document } from '../../lib/documents';
import { Logger } from '../../logger';
import { getPackageInfo } from '../importPackage';
import { getPackageInfo } from '../../importPackage';
import { DocumentSnapshot } from './DocumentSnapshot';
import { createSvelteModuleLoader } from './module-loader';
import { SnapshotManager } from './SnapshotManager';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import * as assert from 'assert';
import sinon from 'sinon';
import { Position } from 'vscode-languageserver';
import { Document } from '../../../src/lib/documents';
import * as importPackage from '../../../src/plugins/importPackage';
import { SvelteDocument, SvelteConfig } from '../../../src/plugins/svelte/SvelteDocument';
import * as importPackage from '../../../src/importPackage';
import { SvelteDocument } from '../../../src/plugins/svelte/SvelteDocument';
import * as configLoader from '../../../src/lib/documents/configLoader';

describe('Svelte Document', () => {
function getSourceCode(transpiled: boolean): string {
Expand All @@ -15,9 +16,11 @@ describe('Svelte Document', () => {
`;
}

function setup(config: SvelteConfig = {}) {
function setup(config: configLoader.SvelteConfig = {}) {
sinon.stub(configLoader, 'loadConfig').returns(config);
const parent = new Document('file:///hello.svelte', getSourceCode(false));
const svelteDoc = new SvelteDocument(parent, config);
sinon.restore();
const svelteDoc = new SvelteDocument(parent);
return { parent, svelteDoc };
}

Expand Down
Loading