178 lines
6.0 KiB
TypeScript
178 lines
6.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {DirectiveInScope, ReferenceSymbol, ShimLocation, Symbol, SymbolKind, VariableSymbol} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
|
import * as ts from 'typescript';
|
|
|
|
|
|
// Reverse mappings of enum would generate strings
|
|
export const ALIAS_NAME = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.aliasName];
|
|
export const SYMBOL_INTERFACE = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.interfaceName];
|
|
export const SYMBOL_PUNC = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.punctuation];
|
|
export const SYMBOL_SPACE = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.space];
|
|
export const SYMBOL_TEXT = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.text];
|
|
|
|
|
|
/**
|
|
* Label for various kinds of Angular entities for TS display info.
|
|
*/
|
|
export enum DisplayInfoKind {
|
|
ATTRIBUTE = 'attribute',
|
|
COMPONENT = 'component',
|
|
DIRECTIVE = 'directive',
|
|
EVENT = 'event',
|
|
REFERENCE = 'reference',
|
|
ELEMENT = 'element',
|
|
VARIABLE = 'variable',
|
|
PIPE = 'pipe',
|
|
PROPERTY = 'property',
|
|
METHOD = 'method',
|
|
TEMPLATE = 'template',
|
|
}
|
|
|
|
export interface DisplayInfo {
|
|
kind: DisplayInfoKind;
|
|
displayParts: ts.SymbolDisplayPart[];
|
|
documentation: ts.SymbolDisplayPart[]|undefined;
|
|
}
|
|
|
|
export function getSymbolDisplayInfo(
|
|
tsLS: ts.LanguageService, typeChecker: ts.TypeChecker,
|
|
symbol: ReferenceSymbol|VariableSymbol): DisplayInfo {
|
|
let kind: DisplayInfoKind;
|
|
if (symbol.kind === SymbolKind.Reference) {
|
|
kind = DisplayInfoKind.REFERENCE;
|
|
} else if (symbol.kind === SymbolKind.Variable) {
|
|
kind = DisplayInfoKind.VARIABLE;
|
|
} else {
|
|
throw new Error(
|
|
`AssertionError: unexpected symbol kind ${SymbolKind[(symbol as Symbol).kind]}`);
|
|
}
|
|
|
|
const displayParts = createDisplayParts(
|
|
symbol.declaration.name, kind, /* containerName */ undefined,
|
|
typeChecker.typeToString(symbol.tsType));
|
|
const documentation = symbol.kind === SymbolKind.Reference ?
|
|
getDocumentationFromTypeDefAtLocation(tsLS, symbol.targetLocation) :
|
|
getDocumentationFromTypeDefAtLocation(tsLS, symbol.initializerLocation);
|
|
return {
|
|
kind,
|
|
displayParts,
|
|
documentation,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Construct a compound `ts.SymbolDisplayPart[]` which incorporates the container and type of a
|
|
* target declaration.
|
|
* @param name Name of the target
|
|
* @param kind component, directive, pipe, etc.
|
|
* @param containerName either the Symbol's container or the NgModule that contains the directive
|
|
* @param type user-friendly name of the type
|
|
* @param documentation docstring or comment
|
|
*/
|
|
export function createDisplayParts(
|
|
name: string, kind: DisplayInfoKind, containerName: string|undefined,
|
|
type: string|undefined): ts.SymbolDisplayPart[] {
|
|
const containerDisplayParts = containerName !== undefined ?
|
|
[
|
|
{text: containerName, kind: SYMBOL_INTERFACE},
|
|
{text: '.', kind: SYMBOL_PUNC},
|
|
] :
|
|
[];
|
|
|
|
const typeDisplayParts = type !== undefined ?
|
|
[
|
|
{text: ':', kind: SYMBOL_PUNC},
|
|
{text: ' ', kind: SYMBOL_SPACE},
|
|
{text: type, kind: SYMBOL_INTERFACE},
|
|
] :
|
|
[];
|
|
return [
|
|
{text: '(', kind: SYMBOL_PUNC},
|
|
{text: kind, kind: SYMBOL_TEXT},
|
|
{text: ')', kind: SYMBOL_PUNC},
|
|
{text: ' ', kind: SYMBOL_SPACE},
|
|
...containerDisplayParts,
|
|
{text: name, kind: SYMBOL_INTERFACE},
|
|
...typeDisplayParts,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Convert a `SymbolDisplayInfoKind` to a `ts.ScriptElementKind` type, allowing it to pass through
|
|
* TypeScript APIs.
|
|
*
|
|
* In practice, this is an "illegal" type cast. Since `ts.ScriptElementKind` is a string, this is
|
|
* safe to do if TypeScript only uses the value in a string context. Consumers of this conversion
|
|
* function are responsible for ensuring this is the case.
|
|
*/
|
|
export function unsafeCastDisplayInfoKindToScriptElementKind(kind: DisplayInfoKind):
|
|
ts.ScriptElementKind {
|
|
return kind as string as ts.ScriptElementKind;
|
|
}
|
|
|
|
function getDocumentationFromTypeDefAtLocation(
|
|
tsLS: ts.LanguageService, shimLocation: ShimLocation): ts.SymbolDisplayPart[]|undefined {
|
|
const typeDefs =
|
|
tsLS.getTypeDefinitionAtPosition(shimLocation.shimPath, shimLocation.positionInShimFile);
|
|
if (typeDefs === undefined || typeDefs.length === 0) {
|
|
return undefined;
|
|
}
|
|
return tsLS.getQuickInfoAtPosition(typeDefs[0].fileName, typeDefs[0].textSpan.start)
|
|
?.documentation;
|
|
}
|
|
|
|
export function getDirectiveDisplayInfo(
|
|
tsLS: ts.LanguageService, dir: DirectiveInScope): DisplayInfo {
|
|
const kind = dir.isComponent ? DisplayInfoKind.COMPONENT : DisplayInfoKind.DIRECTIVE;
|
|
const decl = dir.tsSymbol.declarations.find(ts.isClassDeclaration);
|
|
if (decl === undefined || decl.name === undefined) {
|
|
return {kind, displayParts: [], documentation: []};
|
|
}
|
|
|
|
const res = tsLS.getQuickInfoAtPosition(decl.getSourceFile().fileName, decl.name.getStart());
|
|
if (res === undefined) {
|
|
return {kind, displayParts: [], documentation: []};
|
|
}
|
|
|
|
const displayParts =
|
|
createDisplayParts(dir.tsSymbol.name, kind, dir.ngModule?.name?.text, undefined);
|
|
|
|
return {
|
|
kind,
|
|
displayParts,
|
|
documentation: res.documentation,
|
|
};
|
|
}
|
|
|
|
export function getTsSymbolDisplayInfo(
|
|
tsLS: ts.LanguageService, checker: ts.TypeChecker, symbol: ts.Symbol, kind: DisplayInfoKind,
|
|
ownerName: string|null): DisplayInfo|null {
|
|
const decl = symbol.valueDeclaration;
|
|
if (decl === undefined || (!ts.isPropertyDeclaration(decl) && !ts.isMethodDeclaration(decl)) ||
|
|
!ts.isIdentifier(decl.name)) {
|
|
return null;
|
|
}
|
|
const res = tsLS.getQuickInfoAtPosition(decl.getSourceFile().fileName, decl.name.getStart());
|
|
if (res === undefined) {
|
|
return {kind, displayParts: [], documentation: []};
|
|
}
|
|
|
|
const type = checker.getDeclaredTypeOfSymbol(symbol);
|
|
const typeString = checker.typeToString(type);
|
|
|
|
const displayParts = createDisplayParts(symbol.name, kind, ownerName ?? undefined, typeString);
|
|
|
|
return {
|
|
kind,
|
|
displayParts,
|
|
documentation: res.documentation,
|
|
};
|
|
}
|