UNPKG

@styled/typescript-styled-plugin

Version:

TypeScript language service plugin that adds IntelliSense for styled components

392 lines 17.1 kB
"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