Skip to content

Commit

Permalink
feat: support for TS 4.3
Browse files Browse the repository at this point in the history
close #221
  • Loading branch information
johnsoncodehk committed May 31, 2021
1 parent d566d3f commit a2f5379
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 51 deletions.
6 changes: 6 additions & 0 deletions packages/vscode-typescript-languageservice/src/protocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ declare enum ServerType {
Semantic = 'semantic',
}
declare module 'typescript/lib/protocol' {

interface Response {
readonly _serverType?: ServerType;
}

interface JSDocLinkDisplayPart {
target: Proto.FileSpan;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,22 @@ export function register(languageService: ts.LanguageService, getTextDocument: (
let item: CompletionItem = {
label: entry.name,
labelDetails: {
qualifier: entry.source && path.isAbsolute(entry.source) ? path.relative(rootDir, entry.source) : undefined,
qualifier: entry.source && path.isAbsolute(entry.source) ? path.relative(rootDir, entry.source) : entry.source,
},
kind: convertKind(entry.kind),
sortText: entry.sortText,
insertText: entry.insertText,
preselect: entry.isRecommended,
commitCharacters: getCommitCharacters(entry, info.isNewIdentifierLocation),
data: {
fileName,
offset,
source: entry.source,
name: entry.name,
options: _options,
__volar__: {
fileName,
offset,
source: entry.source,
name: entry.name,
options: _options,
},
...entry.data,
},
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { handleKindModifiers } from './completion';

export function register(languageService: ts.LanguageService, getTextDocument: (uri: string) => TextDocument | undefined, ts: typeof import('typescript')) {
return (item: CompletionItem, newOffset?: number): CompletionItem => {
const fileName = item.data.fileName;
const offset = newOffset ?? item.data.offset;
const name = item.data.name;
const source = item.data.source;
const options = item.data.options;
const fileName = item.data.__volar__.fileName;
const offset = newOffset ?? item.data.__volar__.offset;
const name = item.data.__volar__.name;
const source = item.data.__volar__.source;
const options = item.data.__volar__.options;
let detail: ts.CompletionEntryDetails | undefined;
try {
detail = languageService.getCompletionEntryDetails(fileName, offset, name, {}, source, options);
detail = languageService.getCompletionEntryDetails(fileName, offset, name, {}, source, options, item.data);
}
catch (err) {
item.detail = `[TS Error] ${err}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Position,
} from 'vscode-languageserver/node';
import * as previewer from '../utils/previewer';
import { uriToFsPath } from '@volar/shared';
import { uriToFsPath, fsPathToUri } from '@volar/shared';
import type { TextDocument } from 'vscode-languageserver-textdocument';

export function register(languageService: ts.LanguageService, getTextDocument: (uri: string) => TextDocument | undefined, ts: typeof import('typescript')) {
Expand All @@ -22,7 +22,7 @@ export function register(languageService: ts.LanguageService, getTextDocument: (

const parts: string[] = [];
const displayString = ts.displayPartsToString(info.displayParts);
const documentation = previewer.markdownDocumentation(info.documentation, info.tags);
const documentation = previewer.markdownDocumentation(info.documentation ?? [], info.tags ?? [], { toResource: fsPathToUri });

if (displayString && !documentOnly) {
parts.push(['```typescript', displayString, '```'].join('\n'));
Expand Down
127 changes: 103 additions & 24 deletions packages/vscode-typescript-languageservice/src/utils/previewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

import type * as Proto from '../protocol';

export interface IFilePathToResourceConverter {
/**
* Convert a typescript filepath to a VS Code resource.
*/
toResource(filepath: string): string;
}

function replaceLinks(text: string): string {
return text
// Http(s) links
Expand All @@ -23,7 +30,10 @@ function processInlineTags(text: string): string {
return replaceLinks(text);
}

function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined {
function getTagBodyText(
tag: Proto.JSDocTagInfo,
filePathConverter: IFilePathToResourceConverter,
): string | undefined {
if (!tag.text) {
return undefined;
}
Expand All @@ -36,38 +46,42 @@ function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined {
return '```\n' + text + '\n```';
}

const text = convertLinkTags(tag.text, filePathConverter);
switch (tag.name) {
case 'example':
// check for caption tags, fix for #79704
const captionTagMatches = tag.text.match(/<caption>(.*?)<\/caption>\s*(\r\n|\n)/);
const captionTagMatches = text.match(/<caption>(.*?)<\/caption>\s*(\r\n|\n)/);
if (captionTagMatches && captionTagMatches.index === 0) {
return captionTagMatches[1] + '\n\n' + makeCodeblock(tag.text.substr(captionTagMatches[0].length));
return captionTagMatches[1] + '\n\n' + makeCodeblock(text.substr(captionTagMatches[0].length));
} else {
return makeCodeblock(tag.text);
return makeCodeblock(text);
}
case 'author':
// fix obsucated email address, #80898
const emailMatch = tag.text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/);
const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/);

if (emailMatch === null) {
return tag.text;
return text;
} else {
return `${emailMatch[1]} ${emailMatch[2]}`;
}
case 'default':
return makeCodeblock(tag.text);
return makeCodeblock(text);
}

return processInlineTags(tag.text);
return processInlineTags(text);
}

function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
function getTagDocumentation(
tag: Proto.JSDocTagInfo,
filePathConverter: IFilePathToResourceConverter,
): string | undefined {
switch (tag.name) {
case 'augments':
case 'extends':
case 'param':
case 'template':
const body = (tag.text || '').split(/^(\S+)\s*-?\s*/);
const body = (convertLinkTags(tag.text, filePathConverter)).split(/^(\S+)\s*-?\s*/);
if (body?.length === 3) {
const param = body[1];
const doc = body[2];
Expand All @@ -81,42 +95,107 @@ function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {

// Generic tag
const label = `*@${tag.name}*`;
const text = getTagBodyText(tag);
const text = getTagBodyText(tag, filePathConverter);
if (!text) {
return label;
}
return label + (text.match(/\r\n|\n/g) ? ' \n' + text : ` — ${text}`);
}

export function plain(parts: Proto.SymbolDisplayPart[] | string): string {
return processInlineTags(
typeof parts === 'string'
? parts
: parts.map(part => part.text).join(''));
export function plainWithLinks(
parts: readonly Proto.SymbolDisplayPart[] | string,
filePathConverter: IFilePathToResourceConverter,
): string {
return processInlineTags(convertLinkTags(parts, filePathConverter));
}

/**
* Convert `@link` inline tags to markdown links
*/
function convertLinkTags(
parts: readonly Proto.SymbolDisplayPart[] | string | undefined,
filePathConverter: IFilePathToResourceConverter,
): string {
if (!parts) {
return '';
}

if (typeof parts === 'string') {
return parts;
}

const out: string[] = [];

let currentLink: { name?: string, target?: Proto.FileSpan, text?: string } | undefined;
for (const part of parts) {
switch (part.kind) {
case 'link':
if (currentLink) {
const text = currentLink.text ?? currentLink.name;
if (currentLink.target) {
const link = filePathConverter.toResource(currentLink.target.file) + '#' + `L${currentLink.target.start.line},${currentLink.target.start.offset}`

out.push(`[${text}](${link})`);
} else {
if (text) {
out.push(text);
}
}
currentLink = undefined;
} else {
currentLink = {};
}
break;

case 'linkName':
if (currentLink) {
currentLink.name = part.text;
// TODO: remove cast once we pick up TS 4.3
currentLink.target = (part as any as Proto.JSDocLinkDisplayPart).target;
}
break;

case 'linkText':
if (currentLink) {
currentLink.text = part.text;
}
break;

default:
out.push(part.text);
break;
}
}
return processInlineTags(out.join(''));
}

export function tagsMarkdownPreview(tags: Proto.JSDocTagInfo[]): string {
return tags.map(getTagDocumentation).join(' \n\n');
export function tagsMarkdownPreview(
tags: readonly Proto.JSDocTagInfo[],
filePathConverter: IFilePathToResourceConverter,
): string {
return tags.map(tag => getTagDocumentation(tag, filePathConverter)).join(' \n\n');
}

export function markdownDocumentation(
documentation: Proto.SymbolDisplayPart[] | string | undefined,
tags: Proto.JSDocTagInfo[] | undefined
documentation: Proto.SymbolDisplayPart[] | string,
tags: Proto.JSDocTagInfo[],
filePathConverter: IFilePathToResourceConverter,
): string {
return addMarkdownDocumentation('', documentation, tags);
return addMarkdownDocumentation('', documentation, tags, filePathConverter);
}

export function addMarkdownDocumentation(
out: string,
documentation: Proto.SymbolDisplayPart[] | string | undefined,
tags: Proto.JSDocTagInfo[] | undefined
tags: Proto.JSDocTagInfo[] | undefined,
converter: IFilePathToResourceConverter,
): string {
if (documentation) {
out += plain(documentation);
out += plainWithLinks(documentation, converter);
}

if (tags) {
const tagsPreview = tagsMarkdownPreview(tags);
const tagsPreview = tagsMarkdownPreview(tags, converter);
if (tagsPreview) {
out += '\n\n' + tagsPreview;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export function register({ sourceFiles, tsLanguageService, documentContext, vueH
let tsItems = tsLanguageService.doComplete(sourceMap.mappedDocument.uri, tsRange.start, {
quotePreference,
includeCompletionsForModuleExports: ['script', 'scriptSetup'].includes(tsRange.data.vueTag ?? ''), // TODO: read ts config
includeCompletionsForImportStatements: ['script', 'scriptSetup'].includes(tsRange.data.vueTag ?? ''), // TODO: read ts config
triggerCharacter: context?.triggerCharacter as ts.CompletionsTriggerCharacter,
});
if (tsRange.data.vueTag === 'template') {
Expand Down Expand Up @@ -271,7 +272,7 @@ export function register({ sourceFiles, tsLanguageService, documentContext, vueH
for (const componentName of componentNames) {
const attributes: html.IAttributeData[] = componentName === '*' ? globalAttributes : [];
for (const prop of bind) {
const _name: string = prop.data.name;
const _name: string = prop.data.__volar__.name;
const name = nameCases.attr === 'pascalCase' ? _name : hyphenate(_name);
if (hyphenate(name).startsWith('on-')) {
const propName = '@' + name.substr('on-'.length);
Expand Down Expand Up @@ -299,7 +300,7 @@ export function register({ sourceFiles, tsLanguageService, documentContext, vueH
}
}
for (const event of on) {
const name = nameCases.attr === 'pascalCase' ? event.data.name : hyphenate(event.data.name);
const name = nameCases.attr === 'pascalCase' ? event.data.__volar__.name : hyphenate(event.data.__volar__.name);
const propName = '@' + name;
const propKey = componentName + ':' + propName;
attributes.push({
Expand All @@ -309,7 +310,7 @@ export function register({ sourceFiles, tsLanguageService, documentContext, vueH
tsItems.set(propKey, event);
}
for (const _slot of slot) {
const propName = '#' + _slot.data.name;
const propName = '#' + _slot.data.__volar__.name;
const propKey = componentName + ':' + propName;
slots.push({
name: propName,
Expand Down
14 changes: 7 additions & 7 deletions packages/vscode-vue-languageservice/src/sourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,15 +383,15 @@ export function createSourceFile(
const globalEls = docText.indexOf(SearchTexts.HtmlElements) >= 0 ? tsLanguageService.doComplete(doc.uri, doc.positionAt(doc.getText().indexOf(SearchTexts.HtmlElements))) : [];

components = components.filter(entry => {
const name = entry.data.name as string;
const name = entry.data.__volar__.name as string;
return name.indexOf('$') === -1 && !name.startsWith('_');
});

const contextNames = context.map(entry => entry.data.name);
const componentNames = components.map(entry => entry.data.name);
const propNames = props.map(entry => entry.data.name);
const setupReturnNames = setupReturns.map(entry => entry.data.name);
const htmlElementNames = globalEls.map(entry => entry.data.name);
const contextNames = context.map(entry => entry.data.__volar__.name);
const componentNames = components.map(entry => entry.data.__volar__.name);
const propNames = props.map(entry => entry.data.__volar__.name);
const setupReturnNames = setupReturns.map(entry => entry.data.__volar__.name);
const htmlElementNames = globalEls.map(entry => entry.data.__volar__.name);

if (eqSet(new Set(contextNames), new Set(templateScriptData.context))
&& eqSet(new Set(componentNames), new Set(templateScriptData.components))
Expand Down Expand Up @@ -958,7 +958,7 @@ export function createSourceFile(
const doc = virtualTemplateGen.textDocument.value;
const text = doc.getText();
for (const tag of [...templateScriptData.componentItems, ...templateScriptData.htmlElementItems]) {
const tagName = tag.data.name;
const tagName = tag.data.__volar__.name;
let bind: CompletionItem[] = [];
let on: CompletionItem[] = [];
let slot: CompletionItem[] = [];
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8283,9 +8283,9 @@ typescript-vscode-sh-plugin@^0.6.14:
integrity sha512-AkNlRBbI6K7gk29O92qthNSvc6jjmNQ6isVXoYxkFwPa8D04tIv2SOPd+sd+mNpso4tNdL2gy7nVtrd5yFqvlA==

typescript@latest:
version "4.2.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
version "4.3.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==

uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
Expand Down

0 comments on commit a2f5379

Please sign in to comment.