@styled/typescript-styled-plugin
Version:
TypeScript language service plugin that adds IntelliSense for styled components
392 lines • 17.1 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Original code forked from https://github.com/Quramy/ts-graphql-plugin
Object.defineProperty(exports, "__esModule", { value: true });
exports.StyledTemplateLanguageService = void 0;
const emmet_helper_1 = require("@vscode/emmet-helper");
const vscode_css_languageservice_1 = require("vscode-css-languageservice");
const vscode = require("vscode-languageserver-types");
const config = require("./_config");
const cssErrorCode = 9999;
function arePositionsEqual(left, right) {
return left.line === right.line && left.character === right.character;
}
function isAfter(left, right) {
return right.line > left.line || (right.line === left.line && right.character >= left.character);
}
function overlaps(a, b) {
return !isAfter(a.end, b.start) && !isAfter(b.end, a.start);
}
const emptyCompletionList = {
items: [],
isIncomplete: false,
};
class CompletionsCache {
getCached(context, position) {
if (this._completions &&
context.fileName === this._cachedCompletionsFile &&
this._cachedCompletionsPosition &&
arePositionsEqual(position, this._cachedCompletionsPosition) &&
context.text === this._cachedCompletionsContent) {
return this._completions;
}
return undefined;
}
updateCached(context, position, completions) {
this._cachedCompletionsFile = context.fileName;
this._cachedCompletionsPosition = position;
this._cachedCompletionsContent = context.text;
this._completions = completions;
}
}
class StyledTemplateLanguageService {
constructor(typescript, configurationManager, virtualDocumentFactory, logger // tslint:disable-line
) {
this.typescript = typescript;
this.configurationManager = configurationManager;
this.virtualDocumentFactory = virtualDocumentFactory;
this.logger = logger;
this._completionsCache = new CompletionsCache();
configurationManager.onUpdatedConfig(() => {
if (this._cssLanguageService) {
this._cssLanguageService.configure(this.configurationManager.config);
}
if (this._scssLanguageService) {
this._scssLanguageService.configure(this.configurationManager.config);
}
});
}
get cssLanguageService() {
if (!this._cssLanguageService) {
this._cssLanguageService = (0, vscode_css_languageservice_1.getCSSLanguageService)();
this._cssLanguageService.configure(this.configurationManager.config);
}
return this._cssLanguageService;
}
get scssLanguageService() {
if (!this._scssLanguageService) {
this._scssLanguageService = (0, vscode_css_languageservice_1.getSCSSLanguageService)();
this._scssLanguageService.configure(this.configurationManager.config);
}
return this._scssLanguageService;
}
getCompletionsAtPosition(context, position) {
const items = this.getCompletionItems(context, position);
const doc = this.virtualDocumentFactory.createVirtualDocument(context);
const wrapper = this.virtualDocumentFactory.getVirtualDocumentWrapper(context);
return translateCompletionItemsToCompletionInfo(this.typescript, items, doc, wrapper);
}
getCompletionEntryDetails(context, position, name) {
const item = this.getCompletionItems(context, position).items.find((x) => x.label === name);
if (!item) {
return {
name,
kind: this.typescript.ScriptElementKind.unknown,
kindModifiers: '',
tags: [],
displayParts: toDisplayParts(name),
documentation: [],
};
}
return translateCompletionItemsToCompletionEntryDetails(this.typescript, item);
}
getQuickInfoAtPosition(context, position) {
const doc = this.virtualDocumentFactory.createVirtualDocument(context);
const stylesheet = this.scssLanguageService.parseStylesheet(doc);
const hover = this.scssLanguageService.doHover(doc, this.virtualDocumentFactory.toVirtualDocPosition(position), stylesheet);
if (hover) {
return this.translateHover(hover, this.virtualDocumentFactory.toVirtualDocPosition(position), context);
}
return undefined;
}
getSemanticDiagnostics(context) {
const doc = this.virtualDocumentFactory.createVirtualDocument(context);
const stylesheet = this.scssLanguageService.parseStylesheet(doc);
return this.translateDiagnostics(this.scssLanguageService.doValidation(doc, stylesheet), doc, context, context.text).filter((x) => !!x);
}
getSupportedCodeFixes() {
return [cssErrorCode];
}
getCodeFixesAtPosition(context, start, end
// _errorCodes: number[],
// _format: ts.FormatCodeSettings
) {
const doc = this.virtualDocumentFactory.createVirtualDocument(context);
const stylesheet = this.scssLanguageService.parseStylesheet(doc);
const range = this.toVsRange(context, start, end);
const diagnostics = this.scssLanguageService
.doValidation(doc, stylesheet)
.filter((diagnostic) => overlaps(diagnostic.range, range));
return this.translateCodeActions(context, this.scssLanguageService.doCodeActions(doc, range, { diagnostics }, stylesheet));
}
getOutliningSpans(context) {
const doc = this.virtualDocumentFactory.createVirtualDocument(context);
const ranges = this.scssLanguageService.getFoldingRanges(doc);
return ranges
.filter((range) => {
// Filter out ranges outside on last line
const end = context.toOffset({
line: range.endLine,
character: range.endCharacter || 0,
});
return end < context.text.length;
})
.map((range) => this.translateOutliningSpan(context, range));
}
toVsRange(context, start, end) {
return {
start: this.virtualDocumentFactory.toVirtualDocPosition(context.toPosition(start)),
end: this.virtualDocumentFactory.toVirtualDocPosition(context.toPosition(end)),
};
}
getCompletionItems(context, position) {
const cached = this._completionsCache.getCached(context, position);
const completions = {
isIncomplete: false,
items: [],
};
if (cached) {
return cached;
}
/**
* This would happen if a ` is triggered causing VSCode to open up two ``. At this stage completions aren't needed
* but they are still requested.
* Due to the fact there's nothing to complete (empty template) the language servers below end up requesting everything,
* causing a 3-4 second delay. When a template string is opened up we should do nothing and return an empty list.
*
* Also fixes: https://github.com/styled-components/vscode-styled-components/issues/276
**/
if (context.node.getText() === '``') {
return completions;
}
const doc = this.virtualDocumentFactory.createVirtualDocument(context);
const virtualPosition = this.virtualDocumentFactory.toVirtualDocPosition(position);
const stylesheet = this.scssLanguageService.parseStylesheet(doc);
this.cssLanguageService.setCompletionParticipants([]);
const emmetResults = (0, emmet_helper_1.doComplete)(doc, virtualPosition, 'css', this.configurationManager.config.emmet) || emptyCompletionList;
const completionsCss = this.cssLanguageService.doComplete(doc, virtualPosition, stylesheet) || emptyCompletionList;
const completionsScss = this.scssLanguageService.doComplete(doc, virtualPosition, stylesheet) || emptyCompletionList;
completionsScss.items = filterScssCompletionItems(completionsScss.items);
completions.items = [...completionsCss.items, ...completionsScss.items];
if (emmetResults.items.length) {
completions.items.push(...emmetResults.items);
completions.isIncomplete = true;
}
this._completionsCache.updateCached(context, position, completions);
return completions;
}
translateDiagnostics(diagnostics, doc, context, content) {
const sourceFile = context.node.getSourceFile();
return diagnostics.map((diag) => this.translateDiagnostic(diag, sourceFile, doc, context, content));
}
translateDiagnostic(diagnostic, file, doc, context, content) {
// Make sure returned error is within the real document
if (diagnostic.range.start.line === 0 ||
diagnostic.range.start.line > doc.lineCount ||
diagnostic.range.start.character >= content.length) {
return undefined;
}
const start = context.toOffset(this.virtualDocumentFactory.fromVirtualDocPosition(diagnostic.range.start));
const length = context.toOffset(this.virtualDocumentFactory.fromVirtualDocPosition(diagnostic.range.end)) - start;
const code = typeof diagnostic.code === 'number' ? diagnostic.code : cssErrorCode;
return {
code,
messageText: diagnostic.message,
category: translateSeverity(this.typescript, diagnostic.severity),
file,
start,
length,
source: config.pluginName,
};
}
translateHover(hover, position, context) {
const contents = [];
const convertPart = (hoverContents) => {
if (typeof hoverContents === 'string') {
contents.push({ kind: 'unknown', text: hoverContents });
}
else if (Array.isArray(hoverContents)) {
hoverContents.forEach(convertPart);
}
else {
contents.push({ kind: 'unknown', text: hoverContents.value });
}
};
convertPart(hover.contents);
const start = context.toOffset(this.virtualDocumentFactory.fromVirtualDocPosition(hover.range ? hover.range.start : position));
return {
kind: this.typescript.ScriptElementKind.unknown,
kindModifiers: '',
textSpan: {
start,
length: hover.range
? context.toOffset(this.virtualDocumentFactory.fromVirtualDocPosition(hover.range.end)) - start
: 1,
},
displayParts: [],
documentation: contents,
tags: [],
};
}
translateCodeActions(context, codeActions) {
const actions = [];
for (const vsAction of codeActions) {
if (vsAction.command !== '_css.applyCodeAction') {
continue;
}
const edits = vsAction.arguments && vsAction.arguments[2];
if (edits) {
actions.push({
description: vsAction.title,
changes: edits.map((edit) => this.translateTextEditToFileTextChange(context, edit)),
});
}
}
return actions;
}
translateTextEditToFileTextChange(context, textEdit) {
const start = context.toOffset(this.virtualDocumentFactory.fromVirtualDocPosition(textEdit.range.start));
const end = context.toOffset(this.virtualDocumentFactory.fromVirtualDocPosition(textEdit.range.end));
return {
fileName: context.fileName,
textChanges: [
{
newText: textEdit.newText,
span: {
start,
length: end - start,
},
},
],
};
}
translateOutliningSpan(context, range) {
const startOffset = context.toOffset(this.virtualDocumentFactory.fromVirtualDocPosition({
line: range.startLine,
character: range.startCharacter || 0,
}));
const endOffset = context.toOffset(this.virtualDocumentFactory.fromVirtualDocPosition({
line: range.endLine,
character: range.endCharacter || 0,
}));
const span = {
start: startOffset,
length: endOffset - startOffset,
};
return {
autoCollapse: false,
kind: this.typescript.OutliningSpanKind.Code,
bannerText: '',
textSpan: span,
hintSpan: span,
};
}
}
exports.StyledTemplateLanguageService = StyledTemplateLanguageService;
function filterScssCompletionItems(items) {
return items.filter((item) => item.kind === vscode.CompletionItemKind.Function && item.label.substr(0, 1) === ':');
}
function translateCompletionItemsToCompletionInfo(typescript, items, doc, wrapper) {
return {
metadata: {
isIncomplete: items.isIncomplete,
},
isGlobalCompletion: false,
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: items.items.map((x) => translateCompetionEntry(typescript, x, doc, wrapper)),
};
}
function translateCompletionItemsToCompletionEntryDetails(typescript, item) {
return {
name: item.label,
kind: item.kind ? translateCompletionItemKind(typescript, item.kind) : typescript.ScriptElementKind.unknown,
kindModifiers: getKindModifiers(item),
displayParts: toDisplayParts(item.detail),
documentation: toDisplayParts(item.documentation),
tags: [],
};
}
function translateCompetionEntry(typescript, item, doc, wrapper) {
return {
name: item.label,
kind: item.kind ? translateCompletionItemKind(typescript, item.kind) : typescript.ScriptElementKind.unknown,
kindModifiers: getKindModifiers(item),
sortText: item.sortText || item.label,
replacementSpan: {
// The correct offset for start seems to be the range.start minus the wrapper
start: doc.offsetAt(item.textEdit.range.start) - wrapper.length,
length: doc.offsetAt(item.textEdit.range.end) - doc.offsetAt(item.textEdit.range.start),
},
};
}
function translateCompletionItemKind(typescript, kind) {
switch (kind) {
case vscode.CompletionItemKind.Method:
return typescript.ScriptElementKind.memberFunctionElement;
case vscode.CompletionItemKind.Function:
return typescript.ScriptElementKind.functionElement;
case vscode.CompletionItemKind.Constructor:
return typescript.ScriptElementKind.constructorImplementationElement;
case vscode.CompletionItemKind.Field:
case vscode.CompletionItemKind.Variable:
return typescript.ScriptElementKind.variableElement;
case vscode.CompletionItemKind.Class:
return typescript.ScriptElementKind.classElement;
case vscode.CompletionItemKind.Interface:
return typescript.ScriptElementKind.interfaceElement;
case vscode.CompletionItemKind.Module:
return typescript.ScriptElementKind.moduleElement;
case vscode.CompletionItemKind.Property:
return typescript.ScriptElementKind.memberVariableElement;
case vscode.CompletionItemKind.Unit:
case vscode.CompletionItemKind.Value:
return typescript.ScriptElementKind.constElement;
case vscode.CompletionItemKind.Enum:
return typescript.ScriptElementKind.enumElement;
case vscode.CompletionItemKind.Keyword:
return typescript.ScriptElementKind.keyword;
case vscode.CompletionItemKind.Color:
return typescript.ScriptElementKind.constElement;
case vscode.CompletionItemKind.Reference:
return typescript.ScriptElementKind.alias;
case vscode.CompletionItemKind.File:
return typescript.ScriptElementKind.moduleElement;
case vscode.CompletionItemKind.Snippet:
case vscode.CompletionItemKind.Text:
default:
return typescript.ScriptElementKind.unknown;
}
}
function getKindModifiers(item) {
if (item.kind === vscode.CompletionItemKind.Color) {
return 'color';
}
return '';
}
function translateSeverity(typescript, severity) {
switch (severity) {
case vscode.DiagnosticSeverity.Information:
case vscode.DiagnosticSeverity.Hint:
return typescript.DiagnosticCategory.Message;
case vscode.DiagnosticSeverity.Warning:
return typescript.DiagnosticCategory.Warning;
case vscode.DiagnosticSeverity.Error:
default:
return typescript.DiagnosticCategory.Error;
}
}
function toDisplayParts(text) {
if (!text) {
return [];
}
return [
{
kind: 'text',
text: typeof text === 'string' ? text : text.value,
},
];
}
//# sourceMappingURL=_language-service.js.map