fae2769f44
Both `ReferenceSymbol` and `VariableSymbol` have two locations of interest to an external consumer. 1. The location for the initializers of the local TCB variables allow consumers to query the TypeScript Language Service for information about the initialized type of the variable. 2. The location of the local variable itself (i.e. `_t1`) allows consumers to query the TypeScript LS for references to that variable from within the template. PR Close #39715
129 lines
4.4 KiB
TypeScript
129 lines
4.4 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 {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 {
|
|
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 getDisplayInfo(
|
|
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;
|
|
}
|