mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-13 04:57:55 -06:00
4648 lines
201 KiB
TypeScript
4648 lines
201 KiB
TypeScript
/// <reference path="sys.ts" />
|
|
|
|
/* @internal */
|
|
namespace ts {
|
|
export const externalHelpersModuleNameText = "tslib";
|
|
|
|
export interface ReferencePathMatchResult {
|
|
fileReference?: FileReference;
|
|
diagnosticMessage?: DiagnosticMessage;
|
|
isNoDefaultLib?: boolean;
|
|
isTypeReferenceDirective?: boolean;
|
|
}
|
|
|
|
export function getDeclarationOfKind(symbol: Symbol, kind: SyntaxKind): Declaration {
|
|
const declarations = symbol.declarations;
|
|
if (declarations) {
|
|
for (const declaration of declarations) {
|
|
if (declaration.kind === kind) {
|
|
return declaration;
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export interface StringSymbolWriter extends SymbolWriter {
|
|
string(): string;
|
|
}
|
|
|
|
export interface EmitHost extends ScriptReferenceHost {
|
|
getSourceFiles(): SourceFile[];
|
|
|
|
/* @internal */
|
|
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
|
|
|
|
getCommonSourceDirectory(): string;
|
|
getCanonicalFileName(fileName: string): string;
|
|
getNewLine(): string;
|
|
|
|
isEmitBlocked(emitFileName: string): boolean;
|
|
|
|
writeFile: WriteFileCallback;
|
|
}
|
|
|
|
// Pool writers to avoid needing to allocate them for every symbol we write.
|
|
const stringWriters: StringSymbolWriter[] = [];
|
|
export function getSingleLineStringWriter(): StringSymbolWriter {
|
|
if (stringWriters.length === 0) {
|
|
let str = "";
|
|
|
|
const writeText: (text: string) => void = text => str += text;
|
|
return {
|
|
string: () => str,
|
|
writeKeyword: writeText,
|
|
writeOperator: writeText,
|
|
writePunctuation: writeText,
|
|
writeSpace: writeText,
|
|
writeStringLiteral: writeText,
|
|
writeParameter: writeText,
|
|
writeSymbol: writeText,
|
|
|
|
// Completely ignore indentation for string writers. And map newlines to
|
|
// a single space.
|
|
writeLine: () => str += " ",
|
|
increaseIndent: noop,
|
|
decreaseIndent: noop,
|
|
clear: () => str = "",
|
|
trackSymbol: noop,
|
|
reportInaccessibleThisError: noop
|
|
};
|
|
}
|
|
|
|
return stringWriters.pop();
|
|
}
|
|
|
|
export function releaseStringWriter(writer: StringSymbolWriter) {
|
|
writer.clear();
|
|
stringWriters.push(writer);
|
|
}
|
|
|
|
export function getFullWidth(node: Node) {
|
|
return node.end - node.pos;
|
|
}
|
|
|
|
export function hasResolvedModule(sourceFile: SourceFile, moduleNameText: string): boolean {
|
|
return !!(sourceFile && sourceFile.resolvedModules && sourceFile.resolvedModules[moduleNameText]);
|
|
}
|
|
|
|
export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModuleFull {
|
|
return hasResolvedModule(sourceFile, moduleNameText) ? sourceFile.resolvedModules[moduleNameText] : undefined;
|
|
}
|
|
|
|
export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModuleFull): void {
|
|
if (!sourceFile.resolvedModules) {
|
|
sourceFile.resolvedModules = createMap<ResolvedModuleFull>();
|
|
}
|
|
|
|
sourceFile.resolvedModules[moduleNameText] = resolvedModule;
|
|
}
|
|
|
|
export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective): void {
|
|
if (!sourceFile.resolvedTypeReferenceDirectiveNames) {
|
|
sourceFile.resolvedTypeReferenceDirectiveNames = createMap<ResolvedTypeReferenceDirective>();
|
|
}
|
|
|
|
sourceFile.resolvedTypeReferenceDirectiveNames[typeReferenceDirectiveName] = resolvedTypeReferenceDirective;
|
|
}
|
|
|
|
/* @internal */
|
|
export function moduleResolutionIsEqualTo(oldResolution: ResolvedModuleFull, newResolution: ResolvedModuleFull): boolean {
|
|
return oldResolution.isExternalLibraryImport === newResolution.isExternalLibraryImport &&
|
|
oldResolution.extension === newResolution.extension &&
|
|
oldResolution.resolvedFileName === newResolution.resolvedFileName;
|
|
}
|
|
|
|
/* @internal */
|
|
export function typeDirectiveIsEqualTo(oldResolution: ResolvedTypeReferenceDirective, newResolution: ResolvedTypeReferenceDirective): boolean {
|
|
return oldResolution.resolvedFileName === newResolution.resolvedFileName && oldResolution.primary === newResolution.primary;
|
|
}
|
|
|
|
/* @internal */
|
|
export function hasChangesInResolutions<T>(names: string[], newResolutions: T[], oldResolutions: Map<T>, comparer: (oldResolution: T, newResolution: T) => boolean): boolean {
|
|
if (names.length !== newResolutions.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < names.length; i++) {
|
|
const newResolution = newResolutions[i];
|
|
const oldResolution = oldResolutions && oldResolutions[names[i]];
|
|
const changed =
|
|
oldResolution
|
|
? !newResolution || !comparer(oldResolution, newResolution)
|
|
: newResolution;
|
|
if (changed) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns true if this node contains a parse error anywhere underneath it.
|
|
export function containsParseError(node: Node): boolean {
|
|
aggregateChildData(node);
|
|
return (node.flags & NodeFlags.ThisNodeOrAnySubNodesHasError) !== 0;
|
|
}
|
|
|
|
function aggregateChildData(node: Node): void {
|
|
if (!(node.flags & NodeFlags.HasAggregatedChildData)) {
|
|
// A node is considered to contain a parse error if:
|
|
// a) the parser explicitly marked that it had an error
|
|
// b) any of it's children reported that it had an error.
|
|
const thisNodeOrAnySubNodesHasError = ((node.flags & NodeFlags.ThisNodeHasError) !== 0) ||
|
|
forEachChild(node, containsParseError);
|
|
|
|
// If so, mark ourselves accordingly.
|
|
if (thisNodeOrAnySubNodesHasError) {
|
|
node.flags |= NodeFlags.ThisNodeOrAnySubNodesHasError;
|
|
}
|
|
|
|
// Also mark that we've propagated the child information to this node. This way we can
|
|
// always consult the bit directly on this node without needing to check its children
|
|
// again.
|
|
node.flags |= NodeFlags.HasAggregatedChildData;
|
|
}
|
|
}
|
|
|
|
export function getSourceFileOfNode(node: Node): SourceFile {
|
|
while (node && node.kind !== SyntaxKind.SourceFile) {
|
|
node = node.parent;
|
|
}
|
|
return <SourceFile>node;
|
|
}
|
|
|
|
export function isStatementWithLocals(node: Node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Block:
|
|
case SyntaxKind.CaseBlock:
|
|
case SyntaxKind.ForStatement:
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function getStartPositionOfLine(line: number, sourceFile: SourceFile): number {
|
|
Debug.assert(line >= 0);
|
|
return getLineStarts(sourceFile)[line];
|
|
}
|
|
|
|
// This is a useful function for debugging purposes.
|
|
export function nodePosToString(node: Node): string {
|
|
const file = getSourceFileOfNode(node);
|
|
const loc = getLineAndCharacterOfPosition(file, node.pos);
|
|
return `${file.fileName}(${loc.line + 1},${loc.character + 1})`;
|
|
}
|
|
|
|
export function getStartPosOfNode(node: Node): number {
|
|
return node.pos;
|
|
}
|
|
|
|
export function isDefined(value: any): boolean {
|
|
return value !== undefined;
|
|
}
|
|
|
|
export function getEndLinePosition(line: number, sourceFile: SourceFile): number {
|
|
Debug.assert(line >= 0);
|
|
const lineStarts = getLineStarts(sourceFile);
|
|
|
|
const lineIndex = line;
|
|
const sourceText = sourceFile.text;
|
|
if (lineIndex + 1 === lineStarts.length) {
|
|
// last line - return EOF
|
|
return sourceText.length - 1;
|
|
}
|
|
else {
|
|
// current line start
|
|
const start = lineStarts[lineIndex];
|
|
// take the start position of the next line - 1 = it should be some line break
|
|
let pos = lineStarts[lineIndex + 1] - 1;
|
|
Debug.assert(isLineBreak(sourceText.charCodeAt(pos)));
|
|
// walk backwards skipping line breaks, stop the the beginning of current line.
|
|
// i.e:
|
|
// <some text>
|
|
// $ <- end of line for this position should match the start position
|
|
while (start <= pos && isLineBreak(sourceText.charCodeAt(pos))) {
|
|
pos--;
|
|
}
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
// Returns true if this node is missing from the actual source code. A 'missing' node is different
|
|
// from 'undefined/defined'. When a node is undefined (which can happen for optional nodes
|
|
// in the tree), it is definitely missing. However, a node may be defined, but still be
|
|
// missing. This happens whenever the parser knows it needs to parse something, but can't
|
|
// get anything in the source code that it expects at that location. For example:
|
|
//
|
|
// let a: ;
|
|
//
|
|
// Here, the Type in the Type-Annotation is not-optional (as there is a colon in the source
|
|
// code). So the parser will attempt to parse out a type, and will create an actual node.
|
|
// However, this node will be 'missing' in the sense that no actual source-code/tokens are
|
|
// contained within it.
|
|
export function nodeIsMissing(node: Node) {
|
|
if (node === undefined) {
|
|
return true;
|
|
}
|
|
|
|
return node.pos === node.end && node.pos >= 0 && node.kind !== SyntaxKind.EndOfFileToken;
|
|
}
|
|
|
|
export function nodeIsPresent(node: Node) {
|
|
return !nodeIsMissing(node);
|
|
}
|
|
|
|
export function getTokenPosOfNode(node: Node, sourceFile?: SourceFile, includeJsDocComment?: boolean): number {
|
|
// With nodes that have no width (i.e. 'Missing' nodes), we actually *don't*
|
|
// want to skip trivia because this will launch us forward to the next token.
|
|
if (nodeIsMissing(node)) {
|
|
return node.pos;
|
|
}
|
|
|
|
if (isJSDocNode(node)) {
|
|
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
|
|
}
|
|
|
|
if (includeJsDocComment && node.jsDocComments && node.jsDocComments.length > 0) {
|
|
return getTokenPosOfNode(node.jsDocComments[0]);
|
|
}
|
|
|
|
// For a syntax list, it is possible that one of its children has JSDocComment nodes, while
|
|
// the syntax list itself considers them as normal trivia. Therefore if we simply skip
|
|
// trivia for the list, we may have skipped the JSDocComment as well. So we should process its
|
|
// first child to determine the actual position of its first token.
|
|
if (node.kind === SyntaxKind.SyntaxList && (<SyntaxList>node)._children.length > 0) {
|
|
return getTokenPosOfNode((<SyntaxList>node)._children[0], sourceFile, includeJsDocComment);
|
|
}
|
|
|
|
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos);
|
|
}
|
|
|
|
export function isJSDocNode(node: Node) {
|
|
return node.kind >= SyntaxKind.FirstJSDocNode && node.kind <= SyntaxKind.LastJSDocNode;
|
|
}
|
|
|
|
export function isJSDocTag(node: Node) {
|
|
return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode;
|
|
}
|
|
|
|
export function getNonDecoratorTokenPosOfNode(node: Node, sourceFile?: SourceFile): number {
|
|
if (nodeIsMissing(node) || !node.decorators) {
|
|
return getTokenPosOfNode(node, sourceFile);
|
|
}
|
|
|
|
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.decorators.end);
|
|
}
|
|
|
|
export function getSourceTextOfNodeFromSourceFile(sourceFile: SourceFile, node: Node, includeTrivia = false): string {
|
|
if (nodeIsMissing(node)) {
|
|
return "";
|
|
}
|
|
|
|
const text = sourceFile.text;
|
|
return text.substring(includeTrivia ? node.pos : skipTrivia(text, node.pos), node.end);
|
|
}
|
|
|
|
export function getTextOfNodeFromSourceText(sourceText: string, node: Node): string {
|
|
if (nodeIsMissing(node)) {
|
|
return "";
|
|
}
|
|
|
|
return sourceText.substring(skipTrivia(sourceText, node.pos), node.end);
|
|
}
|
|
|
|
export function getTextOfNode(node: Node, includeTrivia = false): string {
|
|
return getSourceTextOfNodeFromSourceFile(getSourceFileOfNode(node), node, includeTrivia);
|
|
}
|
|
|
|
export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile, languageVersion: ScriptTarget) {
|
|
// Any template literal or string literal with an extended escape
|
|
// (e.g. "\u{0067}") will need to be downleveled as a escaped string literal.
|
|
if (languageVersion < ScriptTarget.ES2015 && (isTemplateLiteralKind(node.kind) || node.hasExtendedUnicodeEscape)) {
|
|
return getQuotedEscapedLiteralText('"', node.text, '"');
|
|
}
|
|
|
|
// If we don't need to downlevel and we can reach the original source text using
|
|
// the node's parent reference, then simply get the text as it was originally written.
|
|
if (!nodeIsSynthesized(node) && node.parent) {
|
|
const text = getSourceTextOfNodeFromSourceFile(sourceFile, node);
|
|
if (languageVersion < ScriptTarget.ES2015 && isBinaryOrOctalIntegerLiteral(node, text)) {
|
|
return node.text;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
// If we can't reach the original source text, use the canonical form if it's a number,
|
|
// or an escaped quoted form of the original text if it's string-like.
|
|
switch (node.kind) {
|
|
case SyntaxKind.StringLiteral:
|
|
return getQuotedEscapedLiteralText('"', node.text, '"');
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
return getQuotedEscapedLiteralText("`", node.text, "`");
|
|
case SyntaxKind.TemplateHead:
|
|
return getQuotedEscapedLiteralText("`", node.text, "${");
|
|
case SyntaxKind.TemplateMiddle:
|
|
return getQuotedEscapedLiteralText("}", node.text, "${");
|
|
case SyntaxKind.TemplateTail:
|
|
return getQuotedEscapedLiteralText("}", node.text, "`");
|
|
case SyntaxKind.NumericLiteral:
|
|
return node.text;
|
|
}
|
|
|
|
Debug.fail(`Literal kind '${node.kind}' not accounted for.`);
|
|
}
|
|
|
|
export function isBinaryOrOctalIntegerLiteral(node: LiteralLikeNode, text: string) {
|
|
if (node.kind === SyntaxKind.NumericLiteral && text.length > 1) {
|
|
switch (text.charCodeAt(1)) {
|
|
case CharacterCodes.b:
|
|
case CharacterCodes.B:
|
|
case CharacterCodes.o:
|
|
case CharacterCodes.O:
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getQuotedEscapedLiteralText(leftQuote: string, text: string, rightQuote: string) {
|
|
return leftQuote + escapeNonAsciiCharacters(escapeString(text)) + rightQuote;
|
|
}
|
|
|
|
// Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__'
|
|
export function escapeIdentifier(identifier: string): string {
|
|
return identifier.length >= 2 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ ? "_" + identifier : identifier;
|
|
}
|
|
|
|
// Remove extra underscore from escaped identifier
|
|
export function unescapeIdentifier(identifier: string): string {
|
|
return identifier.length >= 3 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ && identifier.charCodeAt(2) === CharacterCodes._ ? identifier.substr(1) : identifier;
|
|
}
|
|
|
|
// Make an identifier from an external module name by extracting the string after the last "/" and replacing
|
|
// all non-alphanumeric characters with underscores
|
|
export function makeIdentifierFromModuleName(moduleName: string): string {
|
|
return getBaseFileName(moduleName).replace(/^(\d)/, "_$1").replace(/\W/g, "_");
|
|
}
|
|
|
|
export function isBlockOrCatchScoped(declaration: Declaration) {
|
|
return (getCombinedNodeFlags(declaration) & NodeFlags.BlockScoped) !== 0 ||
|
|
isCatchClauseVariableDeclarationOrBindingElement(declaration);
|
|
}
|
|
|
|
export function isCatchClauseVariableDeclarationOrBindingElement(declaration: Declaration) {
|
|
const node = getRootDeclaration(declaration);
|
|
return node.kind === SyntaxKind.VariableDeclaration && node.parent.kind === SyntaxKind.CatchClause;
|
|
}
|
|
|
|
export function isAmbientModule(node: Node): boolean {
|
|
return node && node.kind === SyntaxKind.ModuleDeclaration &&
|
|
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
|
|
}
|
|
|
|
/** Given a symbol for a module, checks that it is either an untyped import or a shorthand ambient module. */
|
|
export function isShorthandAmbientModuleSymbol(moduleSymbol: Symbol): boolean {
|
|
return isShorthandAmbientModule(moduleSymbol.valueDeclaration);
|
|
}
|
|
|
|
function isShorthandAmbientModule(node: Node): boolean {
|
|
// The only kind of module that can be missing a body is a shorthand ambient module.
|
|
return node.kind === SyntaxKind.ModuleDeclaration && (!(<ModuleDeclaration>node).body);
|
|
}
|
|
|
|
export function isBlockScopedContainerTopLevel(node: Node): boolean {
|
|
return node.kind === SyntaxKind.SourceFile ||
|
|
node.kind === SyntaxKind.ModuleDeclaration ||
|
|
isFunctionLike(node);
|
|
}
|
|
|
|
export function isGlobalScopeAugmentation(module: ModuleDeclaration): boolean {
|
|
return !!(module.flags & NodeFlags.GlobalAugmentation);
|
|
}
|
|
|
|
export function isExternalModuleAugmentation(node: Node): boolean {
|
|
// external module augmentation is a ambient module declaration that is either:
|
|
// - defined in the top level scope and source file is an external module
|
|
// - defined inside ambient module declaration located in the top level scope and source file not an external module
|
|
if (!node || !isAmbientModule(node)) {
|
|
return false;
|
|
}
|
|
switch (node.parent.kind) {
|
|
case SyntaxKind.SourceFile:
|
|
return isExternalModule(<SourceFile>node.parent);
|
|
case SyntaxKind.ModuleBlock:
|
|
return isAmbientModule(node.parent.parent) && !isExternalModule(<SourceFile>node.parent.parent.parent);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isBlockScope(node: Node, parentNode: Node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.SourceFile:
|
|
case SyntaxKind.CaseBlock:
|
|
case SyntaxKind.CatchClause:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.ForStatement:
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
return true;
|
|
|
|
case SyntaxKind.Block:
|
|
// function block is not considered block-scope container
|
|
// see comment in binder.ts: bind(...), case for SyntaxKind.Block
|
|
return parentNode && !isFunctionLike(parentNode);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Gets the nearest enclosing block scope container that has the provided node
|
|
// as a descendant, that is not the provided node.
|
|
export function getEnclosingBlockScopeContainer(node: Node): Node {
|
|
let current = node.parent;
|
|
while (current) {
|
|
if (isBlockScope(current, current.parent)) {
|
|
return current;
|
|
}
|
|
|
|
current = current.parent;
|
|
}
|
|
}
|
|
|
|
// Return display name of an identifier
|
|
// Computed property names will just be emitted as "[<expr>]", where <expr> is the source
|
|
// text of the expression in the computed property.
|
|
export function declarationNameToString(name: DeclarationName) {
|
|
return getFullWidth(name) === 0 ? "(Missing)" : getTextOfNode(name);
|
|
}
|
|
|
|
export function getTextOfPropertyName(name: PropertyName): string {
|
|
switch (name.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return (<Identifier>name).text;
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NumericLiteral:
|
|
return (<LiteralExpression>name).text;
|
|
case SyntaxKind.ComputedPropertyName:
|
|
if (isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind)) {
|
|
return (<LiteralExpression>(<ComputedPropertyName>name).expression).text;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function entityNameToString(name: EntityNameOrEntityNameExpression): string {
|
|
switch (name.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return getFullWidth(name) === 0 ? unescapeIdentifier((<Identifier>name).text) : getTextOfNode(name);
|
|
case SyntaxKind.QualifiedName:
|
|
return entityNameToString((<QualifiedName>name).left) + "." + entityNameToString((<QualifiedName>name).right);
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
return entityNameToString((<PropertyAccessEntityNameExpression>name).expression) + "." + entityNameToString((<PropertyAccessEntityNameExpression>name).name);
|
|
}
|
|
}
|
|
|
|
export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): Diagnostic {
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2);
|
|
}
|
|
|
|
export function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): Diagnostic {
|
|
const span = getErrorSpanForNode(sourceFile, node);
|
|
return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2);
|
|
}
|
|
|
|
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): Diagnostic {
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
const span = getErrorSpanForNode(sourceFile, node);
|
|
return {
|
|
file: sourceFile,
|
|
start: span.start,
|
|
length: span.length,
|
|
code: messageChain.code,
|
|
category: messageChain.category,
|
|
messageText: messageChain.next ? messageChain : messageChain.messageText
|
|
};
|
|
}
|
|
|
|
export function getSpanOfTokenAtPosition(sourceFile: SourceFile, pos: number): TextSpan {
|
|
const scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ true, sourceFile.languageVariant, sourceFile.text, /*onError:*/ undefined, pos);
|
|
scanner.scan();
|
|
const start = scanner.getTokenPos();
|
|
return createTextSpanFromBounds(start, scanner.getTextPos());
|
|
}
|
|
|
|
function getErrorSpanForArrowFunction(sourceFile: SourceFile, node: ArrowFunction): TextSpan {
|
|
const pos = skipTrivia(sourceFile.text, node.pos);
|
|
if (node.body && node.body.kind === SyntaxKind.Block) {
|
|
const { line: startLine } = getLineAndCharacterOfPosition(sourceFile, node.body.pos);
|
|
const { line: endLine } = getLineAndCharacterOfPosition(sourceFile, node.body.end);
|
|
if (startLine < endLine) {
|
|
// The arrow function spans multiple lines,
|
|
// make the error span be the first line, inclusive.
|
|
return createTextSpan(pos, getEndLinePosition(startLine, sourceFile) - pos + 1);
|
|
}
|
|
}
|
|
return createTextSpanFromBounds(pos, node.end);
|
|
}
|
|
|
|
export function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpan {
|
|
let errorNode = node;
|
|
switch (node.kind) {
|
|
case SyntaxKind.SourceFile:
|
|
let pos = skipTrivia(sourceFile.text, 0, /*stopAfterLineBreak*/ false);
|
|
if (pos === sourceFile.text.length) {
|
|
// file is empty - return span for the beginning of the file
|
|
return createTextSpan(0, 0);
|
|
}
|
|
return getSpanOfTokenAtPosition(sourceFile, pos);
|
|
// This list is a work in progress. Add missing node kinds to improve their error
|
|
// spans.
|
|
case SyntaxKind.VariableDeclaration:
|
|
case SyntaxKind.BindingElement:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.EnumMember:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
errorNode = (<Declaration>node).name;
|
|
break;
|
|
case SyntaxKind.ArrowFunction:
|
|
return getErrorSpanForArrowFunction(sourceFile, <ArrowFunction>node);
|
|
}
|
|
|
|
if (errorNode === undefined) {
|
|
// If we don't have a better node, then just set the error on the first token of
|
|
// construct.
|
|
return getSpanOfTokenAtPosition(sourceFile, node.pos);
|
|
}
|
|
|
|
const pos = nodeIsMissing(errorNode)
|
|
? errorNode.pos
|
|
: skipTrivia(sourceFile.text, errorNode.pos);
|
|
|
|
return createTextSpanFromBounds(pos, errorNode.end);
|
|
}
|
|
|
|
export function isExternalOrCommonJsModule(file: SourceFile): boolean {
|
|
return (file.externalModuleIndicator || file.commonJsModuleIndicator) !== undefined;
|
|
}
|
|
|
|
export function isDeclarationFile(file: SourceFile): boolean {
|
|
return file.isDeclarationFile;
|
|
}
|
|
|
|
export function isConstEnumDeclaration(node: Node): boolean {
|
|
return node.kind === SyntaxKind.EnumDeclaration && isConst(node);
|
|
}
|
|
|
|
export function isConst(node: Node): boolean {
|
|
return !!(getCombinedNodeFlags(node) & NodeFlags.Const)
|
|
|| !!(getCombinedModifierFlags(node) & ModifierFlags.Const);
|
|
}
|
|
|
|
export function isLet(node: Node): boolean {
|
|
return !!(getCombinedNodeFlags(node) & NodeFlags.Let);
|
|
}
|
|
|
|
export function isSuperCall(n: Node): n is SuperCall {
|
|
return n.kind === SyntaxKind.CallExpression && (<CallExpression>n).expression.kind === SyntaxKind.SuperKeyword;
|
|
}
|
|
|
|
export function isPrologueDirective(node: Node): boolean {
|
|
return node.kind === SyntaxKind.ExpressionStatement && (<ExpressionStatement>node).expression.kind === SyntaxKind.StringLiteral;
|
|
}
|
|
|
|
export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile) {
|
|
return getLeadingCommentRanges(sourceFileOfNode.text, node.pos);
|
|
}
|
|
|
|
export function getLeadingCommentRangesOfNodeFromText(node: Node, text: string) {
|
|
return getLeadingCommentRanges(text, node.pos);
|
|
}
|
|
|
|
export function getJsDocComments(node: Node, sourceFileOfNode: SourceFile) {
|
|
return getJsDocCommentsFromText(node, sourceFileOfNode.text);
|
|
}
|
|
|
|
export function getJsDocCommentsFromText(node: Node, text: string) {
|
|
const commentRanges = (node.kind === SyntaxKind.Parameter ||
|
|
node.kind === SyntaxKind.TypeParameter ||
|
|
node.kind === SyntaxKind.FunctionExpression ||
|
|
node.kind === SyntaxKind.ArrowFunction) ?
|
|
concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
|
|
getLeadingCommentRangesOfNodeFromText(node, text);
|
|
return filter(commentRanges, isJsDocComment);
|
|
|
|
function isJsDocComment(comment: CommentRange) {
|
|
// True if the comment starts with '/**' but not if it is '/**/'
|
|
return text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk &&
|
|
text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk &&
|
|
text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash;
|
|
}
|
|
}
|
|
|
|
export let fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*<reference\s+path\s*=\s*)('|")(.+?)\2.*?\/>/;
|
|
export let fullTripleSlashReferenceTypeReferenceDirectiveRegEx = /^(\/\/\/\s*<reference\s+types\s*=\s*)('|")(.+?)\2.*?\/>/;
|
|
export let fullTripleSlashAMDReferencePathRegEx = /^(\/\/\/\s*<amd-dependency\s+path\s*=\s*)('|")(.+?)\2.*?\/>/;
|
|
|
|
export function isPartOfTypeNode(node: Node): boolean {
|
|
if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) {
|
|
return true;
|
|
}
|
|
|
|
switch (node.kind) {
|
|
case SyntaxKind.AnyKeyword:
|
|
case SyntaxKind.NumberKeyword:
|
|
case SyntaxKind.StringKeyword:
|
|
case SyntaxKind.BooleanKeyword:
|
|
case SyntaxKind.SymbolKeyword:
|
|
case SyntaxKind.UndefinedKeyword:
|
|
case SyntaxKind.NeverKeyword:
|
|
return true;
|
|
case SyntaxKind.VoidKeyword:
|
|
return node.parent.kind !== SyntaxKind.VoidExpression;
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
return !isExpressionWithTypeArgumentsInClassExtendsClause(node);
|
|
|
|
// Identifiers and qualified names may be type nodes, depending on their context. Climb
|
|
// above them to find the lowest container
|
|
case SyntaxKind.Identifier:
|
|
// If the identifier is the RHS of a qualified name, then it's a type iff its parent is.
|
|
if (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node) {
|
|
node = node.parent;
|
|
}
|
|
else if (node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node) {
|
|
node = node.parent;
|
|
}
|
|
// At this point, node is either a qualified name or an identifier
|
|
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression,
|
|
"'node' was expected to be a qualified name, identifier or property access in 'isPartOfTypeNode'.");
|
|
case SyntaxKind.QualifiedName:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ThisKeyword:
|
|
let parent = node.parent;
|
|
if (parent.kind === SyntaxKind.TypeQuery) {
|
|
return false;
|
|
}
|
|
// Do not recursively call isPartOfTypeNode on the parent. In the example:
|
|
//
|
|
// let a: A.B.C;
|
|
//
|
|
// Calling isPartOfTypeNode would consider the qualified name A.B a type node. Only C or
|
|
// A.B.C is a type node.
|
|
if (SyntaxKind.FirstTypeNode <= parent.kind && parent.kind <= SyntaxKind.LastTypeNode) {
|
|
return true;
|
|
}
|
|
switch (parent.kind) {
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
return !isExpressionWithTypeArgumentsInClassExtendsClause(parent);
|
|
case SyntaxKind.TypeParameter:
|
|
return node === (<TypeParameterDeclaration>parent).constraint;
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.VariableDeclaration:
|
|
return node === (<VariableLikeDeclaration>parent).type;
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
return node === (<FunctionLikeDeclaration>parent).type;
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.IndexSignature:
|
|
return node === (<SignatureDeclaration>parent).type;
|
|
case SyntaxKind.TypeAssertionExpression:
|
|
return node === (<TypeAssertion>parent).type;
|
|
case SyntaxKind.CallExpression:
|
|
case SyntaxKind.NewExpression:
|
|
return (<CallExpression>parent).typeArguments && indexOf((<CallExpression>parent).typeArguments, node) >= 0;
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
// TODO (drosen): TaggedTemplateExpressions may eventually support type arguments.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Warning: This has the same semantics as the forEach family of functions,
|
|
// in that traversal terminates in the event that 'visitor' supplies a truthy value.
|
|
export function forEachReturnStatement<T>(body: Block, visitor: (stmt: ReturnStatement) => T): T {
|
|
|
|
return traverse(body);
|
|
|
|
function traverse(node: Node): T {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ReturnStatement:
|
|
return visitor(<ReturnStatement>node);
|
|
case SyntaxKind.CaseBlock:
|
|
case SyntaxKind.Block:
|
|
case SyntaxKind.IfStatement:
|
|
case SyntaxKind.DoStatement:
|
|
case SyntaxKind.WhileStatement:
|
|
case SyntaxKind.ForStatement:
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
case SyntaxKind.WithStatement:
|
|
case SyntaxKind.SwitchStatement:
|
|
case SyntaxKind.CaseClause:
|
|
case SyntaxKind.DefaultClause:
|
|
case SyntaxKind.LabeledStatement:
|
|
case SyntaxKind.TryStatement:
|
|
case SyntaxKind.CatchClause:
|
|
return forEachChild(node, traverse);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function forEachYieldExpression(body: Block, visitor: (expr: YieldExpression) => void): void {
|
|
|
|
return traverse(body);
|
|
|
|
function traverse(node: Node): void {
|
|
switch (node.kind) {
|
|
case SyntaxKind.YieldExpression:
|
|
visitor(<YieldExpression>node);
|
|
let operand = (<YieldExpression>node).expression;
|
|
if (operand) {
|
|
traverse(operand);
|
|
}
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
// These are not allowed inside a generator now, but eventually they may be allowed
|
|
// as local types. Regardless, any yield statements contained within them should be
|
|
// skipped in this traversal.
|
|
return;
|
|
default:
|
|
if (isFunctionLike(node)) {
|
|
const name = (<FunctionLikeDeclaration>node).name;
|
|
if (name && name.kind === SyntaxKind.ComputedPropertyName) {
|
|
// Note that we will not include methods/accessors of a class because they would require
|
|
// first descending into the class. This is by design.
|
|
traverse((<ComputedPropertyName>name).expression);
|
|
return;
|
|
}
|
|
}
|
|
else if (!isPartOfTypeNode(node)) {
|
|
// This is the general case, which should include mostly expressions and statements.
|
|
// Also includes NodeArrays.
|
|
forEachChild(node, traverse);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
export function isVariableLike(node: Node): node is VariableLikeDeclaration {
|
|
if (node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.BindingElement:
|
|
case SyntaxKind.EnumMember:
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
case SyntaxKind.VariableDeclaration:
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isAccessor(node: Node): node is AccessorDeclaration {
|
|
return node && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor);
|
|
}
|
|
|
|
export function isClassLike(node: Node): node is ClassLikeDeclaration {
|
|
return node && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression);
|
|
}
|
|
|
|
export function isFunctionLike(node: Node): node is FunctionLikeDeclaration {
|
|
return node && isFunctionLikeKind(node.kind);
|
|
}
|
|
|
|
export function isFunctionLikeKind(kind: SyntaxKind): boolean {
|
|
switch (kind) {
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.IndexSignature:
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function introducesArgumentsExoticObject(node: Node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isIterationStatement(node: Node, lookInLabeledStatements: boolean): node is IterationStatement {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ForStatement:
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
case SyntaxKind.DoStatement:
|
|
case SyntaxKind.WhileStatement:
|
|
return true;
|
|
case SyntaxKind.LabeledStatement:
|
|
return lookInLabeledStatements && isIterationStatement((<LabeledStatement>node).statement, lookInLabeledStatements);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
export function isFunctionBlock(node: Node) {
|
|
return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent);
|
|
}
|
|
|
|
export function isObjectLiteralMethod(node: Node): node is MethodDeclaration {
|
|
return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression;
|
|
}
|
|
|
|
export function isObjectLiteralOrClassExpressionMethod(node: Node): node is MethodDeclaration {
|
|
return node.kind === SyntaxKind.MethodDeclaration &&
|
|
(node.parent.kind === SyntaxKind.ObjectLiteralExpression ||
|
|
node.parent.kind === SyntaxKind.ClassExpression);
|
|
}
|
|
|
|
export function isIdentifierTypePredicate(predicate: TypePredicate): predicate is IdentifierTypePredicate {
|
|
return predicate && predicate.kind === TypePredicateKind.Identifier;
|
|
}
|
|
|
|
export function isThisTypePredicate(predicate: TypePredicate): predicate is ThisTypePredicate {
|
|
return predicate && predicate.kind === TypePredicateKind.This;
|
|
}
|
|
|
|
export function getContainingFunction(node: Node): FunctionLikeDeclaration {
|
|
while (true) {
|
|
node = node.parent;
|
|
if (!node || isFunctionLike(node)) {
|
|
return <FunctionLikeDeclaration>node;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getContainingClass(node: Node): ClassLikeDeclaration {
|
|
while (true) {
|
|
node = node.parent;
|
|
if (!node || isClassLike(node)) {
|
|
return <ClassLikeDeclaration>node;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getThisContainer(node: Node, includeArrowFunctions: boolean): Node {
|
|
while (true) {
|
|
node = node.parent;
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
switch (node.kind) {
|
|
case SyntaxKind.ComputedPropertyName:
|
|
// If the grandparent node is an object literal (as opposed to a class),
|
|
// then the computed property is not a 'this' container.
|
|
// A computed property name in a class needs to be a this container
|
|
// so that we can error on it.
|
|
if (isClassLike(node.parent.parent)) {
|
|
return node;
|
|
}
|
|
// If this is a computed property, then the parent should not
|
|
// make it a this container. The parent might be a property
|
|
// in an object literal, like a method or accessor. But in order for
|
|
// such a parent to be a this container, the reference must be in
|
|
// the *body* of the container.
|
|
node = node.parent;
|
|
break;
|
|
case SyntaxKind.Decorator:
|
|
// Decorators are always applied outside of the body of a class or method.
|
|
if (node.parent.kind === SyntaxKind.Parameter && isClassElement(node.parent.parent)) {
|
|
// If the decorator's parent is a Parameter, we resolve the this container from
|
|
// the grandparent class declaration.
|
|
node = node.parent.parent;
|
|
}
|
|
else if (isClassElement(node.parent)) {
|
|
// If the decorator's parent is a class element, we resolve the 'this' container
|
|
// from the parent class declaration.
|
|
node = node.parent;
|
|
}
|
|
break;
|
|
case SyntaxKind.ArrowFunction:
|
|
if (!includeArrowFunctions) {
|
|
continue;
|
|
}
|
|
// Fall through
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.IndexSignature:
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.SourceFile:
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given an super call/property node, returns the closest node where
|
|
* - a super call/property access is legal in the node and not legal in the parent node the node.
|
|
* i.e. super call is legal in constructor but not legal in the class body.
|
|
* - the container is an arrow function (so caller might need to call getSuperContainer again in case it needs to climb higher)
|
|
* - a super call/property is definitely illegal in the container (but might be legal in some subnode)
|
|
* i.e. super property access is illegal in function declaration but can be legal in the statement list
|
|
*/
|
|
export function getSuperContainer(node: Node, stopOnFunctions: boolean): Node {
|
|
while (true) {
|
|
node = node.parent;
|
|
if (!node) {
|
|
return node;
|
|
}
|
|
switch (node.kind) {
|
|
case SyntaxKind.ComputedPropertyName:
|
|
node = node.parent;
|
|
break;
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
if (!stopOnFunctions) {
|
|
continue;
|
|
}
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
return node;
|
|
case SyntaxKind.Decorator:
|
|
// Decorators are always applied outside of the body of a class or method.
|
|
if (node.parent.kind === SyntaxKind.Parameter && isClassElement(node.parent.parent)) {
|
|
// If the decorator's parent is a Parameter, we resolve the this container from
|
|
// the grandparent class declaration.
|
|
node = node.parent.parent;
|
|
}
|
|
else if (isClassElement(node.parent)) {
|
|
// If the decorator's parent is a class element, we resolve the 'this' container
|
|
// from the parent class declaration.
|
|
node = node.parent;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getImmediatelyInvokedFunctionExpression(func: Node): CallExpression {
|
|
if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) {
|
|
let prev = func;
|
|
let parent = func.parent;
|
|
while (parent.kind === SyntaxKind.ParenthesizedExpression) {
|
|
prev = parent;
|
|
parent = parent.parent;
|
|
}
|
|
if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) {
|
|
return parent as CallExpression;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether a node is a property or element access expression for super.
|
|
*/
|
|
export function isSuperProperty(node: Node): node is SuperProperty {
|
|
const kind = node.kind;
|
|
return (kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression)
|
|
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
|
|
}
|
|
|
|
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression {
|
|
switch (node.kind) {
|
|
case SyntaxKind.TypeReference:
|
|
case SyntaxKind.JSDocTypeReference:
|
|
return (<TypeReferenceNode>node).typeName;
|
|
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
return isEntityNameExpression((<ExpressionWithTypeArguments>node).expression)
|
|
? <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression
|
|
: undefined;
|
|
|
|
case SyntaxKind.Identifier:
|
|
case SyntaxKind.QualifiedName:
|
|
return (<EntityName><Node>node);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function isCallLikeExpression(node: Node): node is CallLikeExpression {
|
|
switch (node.kind) {
|
|
case SyntaxKind.CallExpression:
|
|
case SyntaxKind.NewExpression:
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
case SyntaxKind.Decorator:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function getInvokedExpression(node: CallLikeExpression): Expression {
|
|
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
|
|
return (<TaggedTemplateExpression>node).tag;
|
|
}
|
|
|
|
// Will either be a CallExpression, NewExpression, or Decorator.
|
|
return (<CallExpression | Decorator>node).expression;
|
|
}
|
|
|
|
export function nodeCanBeDecorated(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
// classes are valid targets
|
|
return true;
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
// property declarations are valid if their parent is a class declaration.
|
|
return node.parent.kind === SyntaxKind.ClassDeclaration;
|
|
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.MethodDeclaration:
|
|
// if this method has a body and its parent is a class declaration, this is a valid target.
|
|
return (<FunctionLikeDeclaration>node).body !== undefined
|
|
&& node.parent.kind === SyntaxKind.ClassDeclaration;
|
|
|
|
case SyntaxKind.Parameter:
|
|
// if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target;
|
|
return (<FunctionLikeDeclaration>node.parent).body !== undefined
|
|
&& (node.parent.kind === SyntaxKind.Constructor
|
|
|| node.parent.kind === SyntaxKind.MethodDeclaration
|
|
|| node.parent.kind === SyntaxKind.SetAccessor)
|
|
&& node.parent.parent.kind === SyntaxKind.ClassDeclaration;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function nodeIsDecorated(node: Node): boolean {
|
|
return node.decorators !== undefined
|
|
&& nodeCanBeDecorated(node);
|
|
}
|
|
|
|
export function nodeOrChildIsDecorated(node: Node): boolean {
|
|
return nodeIsDecorated(node) || childIsDecorated(node);
|
|
}
|
|
|
|
export function childIsDecorated(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
return forEach((<ClassDeclaration>node).members, nodeOrChildIsDecorated);
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.SetAccessor:
|
|
return forEach((<FunctionLikeDeclaration>node).parameters, nodeIsDecorated);
|
|
}
|
|
}
|
|
|
|
export function isJSXTagName(node: Node) {
|
|
const parent = node.parent;
|
|
if (parent.kind === SyntaxKind.JsxOpeningElement ||
|
|
parent.kind === SyntaxKind.JsxSelfClosingElement ||
|
|
parent.kind === SyntaxKind.JsxClosingElement) {
|
|
return (<JsxOpeningLikeElement>parent).tagName === node;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isPartOfExpression(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ThisKeyword:
|
|
case SyntaxKind.SuperKeyword:
|
|
case SyntaxKind.NullKeyword:
|
|
case SyntaxKind.TrueKeyword:
|
|
case SyntaxKind.FalseKeyword:
|
|
case SyntaxKind.RegularExpressionLiteral:
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
case SyntaxKind.CallExpression:
|
|
case SyntaxKind.NewExpression:
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
case SyntaxKind.AsExpression:
|
|
case SyntaxKind.TypeAssertionExpression:
|
|
case SyntaxKind.NonNullExpression:
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.VoidExpression:
|
|
case SyntaxKind.DeleteExpression:
|
|
case SyntaxKind.TypeOfExpression:
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
case SyntaxKind.PostfixUnaryExpression:
|
|
case SyntaxKind.BinaryExpression:
|
|
case SyntaxKind.ConditionalExpression:
|
|
case SyntaxKind.SpreadElement:
|
|
case SyntaxKind.TemplateExpression:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
case SyntaxKind.OmittedExpression:
|
|
case SyntaxKind.JsxElement:
|
|
case SyntaxKind.JsxSelfClosingElement:
|
|
case SyntaxKind.YieldExpression:
|
|
case SyntaxKind.AwaitExpression:
|
|
return true;
|
|
case SyntaxKind.QualifiedName:
|
|
while (node.parent.kind === SyntaxKind.QualifiedName) {
|
|
node = node.parent;
|
|
}
|
|
return node.parent.kind === SyntaxKind.TypeQuery || isJSXTagName(node);
|
|
case SyntaxKind.Identifier:
|
|
if (node.parent.kind === SyntaxKind.TypeQuery || isJSXTagName(node)) {
|
|
return true;
|
|
}
|
|
// fall through
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.ThisKeyword:
|
|
let parent = node.parent;
|
|
switch (parent.kind) {
|
|
case SyntaxKind.VariableDeclaration:
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.EnumMember:
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.BindingElement:
|
|
return (<VariableLikeDeclaration>parent).initializer === node;
|
|
case SyntaxKind.ExpressionStatement:
|
|
case SyntaxKind.IfStatement:
|
|
case SyntaxKind.DoStatement:
|
|
case SyntaxKind.WhileStatement:
|
|
case SyntaxKind.ReturnStatement:
|
|
case SyntaxKind.WithStatement:
|
|
case SyntaxKind.SwitchStatement:
|
|
case SyntaxKind.CaseClause:
|
|
case SyntaxKind.ThrowStatement:
|
|
case SyntaxKind.SwitchStatement:
|
|
return (<ExpressionStatement>parent).expression === node;
|
|
case SyntaxKind.ForStatement:
|
|
let forStatement = <ForStatement>parent;
|
|
return (forStatement.initializer === node && forStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) ||
|
|
forStatement.condition === node ||
|
|
forStatement.incrementor === node;
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
let forInStatement = <ForInStatement | ForOfStatement>parent;
|
|
return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) ||
|
|
forInStatement.expression === node;
|
|
case SyntaxKind.TypeAssertionExpression:
|
|
case SyntaxKind.AsExpression:
|
|
return node === (<AssertionExpression>parent).expression;
|
|
case SyntaxKind.TemplateSpan:
|
|
return node === (<TemplateSpan>parent).expression;
|
|
case SyntaxKind.ComputedPropertyName:
|
|
return node === (<ComputedPropertyName>parent).expression;
|
|
case SyntaxKind.Decorator:
|
|
case SyntaxKind.JsxExpression:
|
|
case SyntaxKind.JsxSpreadAttribute:
|
|
return true;
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
return (<ExpressionWithTypeArguments>parent).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent);
|
|
default:
|
|
if (isPartOfExpression(parent)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) {
|
|
const moduleState = getModuleInstanceState(node);
|
|
return moduleState === ModuleInstanceState.Instantiated ||
|
|
(preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly);
|
|
}
|
|
|
|
export function isExternalModuleImportEqualsDeclaration(node: Node) {
|
|
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference;
|
|
}
|
|
|
|
export function getExternalModuleImportEqualsDeclarationExpression(node: Node) {
|
|
Debug.assert(isExternalModuleImportEqualsDeclaration(node));
|
|
return (<ExternalModuleReference>(<ImportEqualsDeclaration>node).moduleReference).expression;
|
|
}
|
|
|
|
export function isInternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration {
|
|
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind !== SyntaxKind.ExternalModuleReference;
|
|
}
|
|
|
|
export function isSourceFileJavaScript(file: SourceFile): boolean {
|
|
return isInJavaScriptFile(file);
|
|
}
|
|
|
|
export function isInJavaScriptFile(node: Node): boolean {
|
|
return node && !!(node.flags & NodeFlags.JavaScriptFile);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the node is a CallExpression to the identifier 'require' with
|
|
* exactly one argument.
|
|
* This function does not test if the node is in a JavaScript file or not.
|
|
*/
|
|
export function isRequireCall(expression: Node, checkArgumentIsStringLiteral: boolean): expression is CallExpression {
|
|
// of the form 'require("name")'
|
|
const isRequire = expression.kind === SyntaxKind.CallExpression &&
|
|
(<CallExpression>expression).expression.kind === SyntaxKind.Identifier &&
|
|
(<Identifier>(<CallExpression>expression).expression).text === "require" &&
|
|
(<CallExpression>expression).arguments.length === 1;
|
|
|
|
return isRequire && (!checkArgumentIsStringLiteral || (<CallExpression>expression).arguments[0].kind === SyntaxKind.StringLiteral);
|
|
}
|
|
|
|
export function isSingleOrDoubleQuote(charCode: number) {
|
|
return charCode === CharacterCodes.singleQuote || charCode === CharacterCodes.doubleQuote;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the node is a variable declaration whose initializer is a function expression.
|
|
* This function does not test if the node is in a JavaScript file or not.
|
|
*/
|
|
export function isDeclarationOfFunctionExpression(s: Symbol) {
|
|
if (s.valueDeclaration && s.valueDeclaration.kind === SyntaxKind.VariableDeclaration) {
|
|
const declaration = s.valueDeclaration as VariableDeclaration;
|
|
return declaration.initializer && declaration.initializer.kind === SyntaxKind.FunctionExpression;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
|
|
/// assignments we treat as special in the binder
|
|
export function getSpecialPropertyAssignmentKind(expression: Node): SpecialPropertyAssignmentKind {
|
|
if (!isInJavaScriptFile(expression)) {
|
|
return SpecialPropertyAssignmentKind.None;
|
|
}
|
|
if (expression.kind !== SyntaxKind.BinaryExpression) {
|
|
return SpecialPropertyAssignmentKind.None;
|
|
}
|
|
const expr = <BinaryExpression>expression;
|
|
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || expr.left.kind !== SyntaxKind.PropertyAccessExpression) {
|
|
return SpecialPropertyAssignmentKind.None;
|
|
}
|
|
const lhs = <PropertyAccessExpression>expr.left;
|
|
if (lhs.expression.kind === SyntaxKind.Identifier) {
|
|
const lhsId = <Identifier>lhs.expression;
|
|
if (lhsId.text === "exports") {
|
|
// exports.name = expr
|
|
return SpecialPropertyAssignmentKind.ExportsProperty;
|
|
}
|
|
else if (lhsId.text === "module" && lhs.name.text === "exports") {
|
|
// module.exports = expr
|
|
return SpecialPropertyAssignmentKind.ModuleExports;
|
|
}
|
|
}
|
|
else if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
|
|
return SpecialPropertyAssignmentKind.ThisProperty;
|
|
}
|
|
else if (lhs.expression.kind === SyntaxKind.PropertyAccessExpression) {
|
|
// chained dot, e.g. x.y.z = expr; this var is the 'x.y' part
|
|
const innerPropertyAccess = <PropertyAccessExpression>lhs.expression;
|
|
if (innerPropertyAccess.expression.kind === SyntaxKind.Identifier) {
|
|
// module.exports.name = expr
|
|
const innerPropertyAccessIdentifier = <Identifier>innerPropertyAccess.expression;
|
|
if (innerPropertyAccessIdentifier.text === "module" && innerPropertyAccess.name.text === "exports") {
|
|
return SpecialPropertyAssignmentKind.ExportsProperty;
|
|
}
|
|
if (innerPropertyAccess.name.text === "prototype") {
|
|
return SpecialPropertyAssignmentKind.PrototypeProperty;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SpecialPropertyAssignmentKind.None;
|
|
}
|
|
|
|
export function getExternalModuleName(node: Node): Expression {
|
|
if (node.kind === SyntaxKind.ImportDeclaration) {
|
|
return (<ImportDeclaration>node).moduleSpecifier;
|
|
}
|
|
if (node.kind === SyntaxKind.ImportEqualsDeclaration) {
|
|
const reference = (<ImportEqualsDeclaration>node).moduleReference;
|
|
if (reference.kind === SyntaxKind.ExternalModuleReference) {
|
|
return (<ExternalModuleReference>reference).expression;
|
|
}
|
|
}
|
|
if (node.kind === SyntaxKind.ExportDeclaration) {
|
|
return (<ExportDeclaration>node).moduleSpecifier;
|
|
}
|
|
if (node.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral) {
|
|
return (<ModuleDeclaration>node).name;
|
|
}
|
|
}
|
|
|
|
export function getNamespaceDeclarationNode(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): ImportEqualsDeclaration | NamespaceImport {
|
|
if (node.kind === SyntaxKind.ImportEqualsDeclaration) {
|
|
return <ImportEqualsDeclaration>node;
|
|
}
|
|
|
|
const importClause = (<ImportDeclaration>node).importClause;
|
|
if (importClause && importClause.namedBindings && importClause.namedBindings.kind === SyntaxKind.NamespaceImport) {
|
|
return <NamespaceImport>importClause.namedBindings;
|
|
}
|
|
}
|
|
|
|
export function isDefaultImport(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration) {
|
|
return node.kind === SyntaxKind.ImportDeclaration
|
|
&& (<ImportDeclaration>node).importClause
|
|
&& !!(<ImportDeclaration>node).importClause.name;
|
|
}
|
|
|
|
export function hasQuestionToken(node: Node) {
|
|
if (node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
return (<ParameterDeclaration | MethodDeclaration | PropertyDeclaration>node).questionToken !== undefined;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function isJSDocConstructSignature(node: Node) {
|
|
return node.kind === SyntaxKind.JSDocFunctionType &&
|
|
(<JSDocFunctionType>node).parameters.length > 0 &&
|
|
(<JSDocFunctionType>node).parameters[0].type.kind === SyntaxKind.JSDocConstructorType;
|
|
}
|
|
|
|
function getJSDocTag(node: Node, kind: SyntaxKind, checkParentVariableStatement: boolean): JSDocTag {
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
|
|
const jsDocTags = getJSDocTags(node, checkParentVariableStatement);
|
|
if (!jsDocTags) {
|
|
return undefined;
|
|
}
|
|
|
|
for (const tag of jsDocTags) {
|
|
if (tag.kind === kind) {
|
|
return tag;
|
|
}
|
|
}
|
|
}
|
|
|
|
function append<T>(previous: T[] | undefined, additional: T[] | undefined): T[] | undefined {
|
|
if (additional) {
|
|
if (!previous) {
|
|
previous = [];
|
|
}
|
|
for (const x of additional) {
|
|
previous.push(x);
|
|
}
|
|
}
|
|
return previous;
|
|
}
|
|
|
|
export function getJSDocComments(node: Node, checkParentVariableStatement: boolean): string[] {
|
|
return getJSDocs(node, checkParentVariableStatement, docs => map(docs, doc => doc.comment), tags => map(tags, tag => tag.comment));
|
|
}
|
|
|
|
function getJSDocTags(node: Node, checkParentVariableStatement: boolean): JSDocTag[] {
|
|
return getJSDocs(node, checkParentVariableStatement, docs => {
|
|
const result: JSDocTag[] = [];
|
|
for (const doc of docs) {
|
|
if (doc.tags) {
|
|
result.push(...doc.tags);
|
|
}
|
|
}
|
|
return result;
|
|
}, tags => tags);
|
|
}
|
|
|
|
function getJSDocs<T>(node: Node, checkParentVariableStatement: boolean, getDocs: (docs: JSDoc[]) => T[], getTags: (tags: JSDocTag[]) => T[]): T[] {
|
|
// TODO: Get rid of getJsDocComments and friends (note the lowercase 's' in Js)
|
|
// TODO: A lot of this work should be cached, maybe. I guess it's only used in services right now...
|
|
let result: T[] = undefined;
|
|
// prepend documentation from parent sources
|
|
if (checkParentVariableStatement) {
|
|
// Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement.
|
|
// /**
|
|
// * @param {number} name
|
|
// * @returns {number}
|
|
// */
|
|
// var x = function(name) { return name.length; }
|
|
const isInitializerOfVariableDeclarationInStatement =
|
|
isVariableLike(node.parent) &&
|
|
(node.parent).initializer === node &&
|
|
node.parent.parent.parent.kind === SyntaxKind.VariableStatement;
|
|
const isVariableOfVariableDeclarationStatement = isVariableLike(node) &&
|
|
node.parent.parent.kind === SyntaxKind.VariableStatement;
|
|
|
|
const variableStatementNode =
|
|
isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent :
|
|
isVariableOfVariableDeclarationStatement ? node.parent.parent :
|
|
undefined;
|
|
if (variableStatementNode) {
|
|
result = append(result, getJSDocs(variableStatementNode, checkParentVariableStatement, getDocs, getTags));
|
|
}
|
|
if (node.kind === SyntaxKind.ModuleDeclaration &&
|
|
node.parent && node.parent.kind === SyntaxKind.ModuleDeclaration) {
|
|
result = append(result, getJSDocs(node.parent, checkParentVariableStatement, getDocs, getTags));
|
|
}
|
|
|
|
// Also recognize when the node is the RHS of an assignment expression
|
|
const parent = node.parent;
|
|
const isSourceOfAssignmentExpressionStatement =
|
|
parent && parent.parent &&
|
|
parent.kind === SyntaxKind.BinaryExpression &&
|
|
(parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
parent.parent.kind === SyntaxKind.ExpressionStatement;
|
|
if (isSourceOfAssignmentExpressionStatement) {
|
|
result = append(result, getJSDocs(parent.parent, checkParentVariableStatement, getDocs, getTags));
|
|
}
|
|
|
|
const isPropertyAssignmentExpression = parent && parent.kind === SyntaxKind.PropertyAssignment;
|
|
if (isPropertyAssignmentExpression) {
|
|
result = append(result, getJSDocs(parent, checkParentVariableStatement, getDocs, getTags));
|
|
}
|
|
|
|
// Pull parameter comments from declaring function as well
|
|
if (node.kind === SyntaxKind.Parameter) {
|
|
const paramTags = getJSDocParameterTag(node as ParameterDeclaration, checkParentVariableStatement);
|
|
if (paramTags) {
|
|
result = append(result, getTags(paramTags));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isVariableLike(node) && node.initializer) {
|
|
result = append(result, getJSDocs(node.initializer, /*checkParentVariableStatement*/ false, getDocs, getTags));
|
|
}
|
|
|
|
if (node.jsDocComments) {
|
|
if (result) {
|
|
result = append(result, getDocs(node.jsDocComments));
|
|
}
|
|
else {
|
|
return getDocs(node.jsDocComments);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function getJSDocParameterTag(param: ParameterDeclaration, checkParentVariableStatement: boolean): JSDocTag[] {
|
|
const func = param.parent as FunctionLikeDeclaration;
|
|
const tags = getJSDocTags(func, checkParentVariableStatement);
|
|
if (!param.name) {
|
|
// this is an anonymous jsdoc param from a `function(type1, type2): type3` specification
|
|
const i = func.parameters.indexOf(param);
|
|
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag);
|
|
if (paramTags && 0 <= i && i < paramTags.length) {
|
|
return [paramTags[i]];
|
|
}
|
|
}
|
|
else if (param.name.kind === SyntaxKind.Identifier) {
|
|
const name = (param.name as Identifier).text;
|
|
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag && (tag as JSDocParameterTag).parameterName.text === name);
|
|
if (paramTags) {
|
|
return paramTags;
|
|
}
|
|
}
|
|
else {
|
|
// TODO: it's a destructured parameter, so it should look up an "object type" series of multiple lines
|
|
// But multi-line object types aren't supported yet either
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export function getJSDocTypeTag(node: Node): JSDocTypeTag {
|
|
return <JSDocTypeTag>getJSDocTag(node, SyntaxKind.JSDocTypeTag, /*checkParentVariableStatement*/ false);
|
|
}
|
|
|
|
export function getJSDocReturnTag(node: Node): JSDocReturnTag {
|
|
return <JSDocReturnTag>getJSDocTag(node, SyntaxKind.JSDocReturnTag, /*checkParentVariableStatement*/ true);
|
|
}
|
|
|
|
export function getJSDocTemplateTag(node: Node): JSDocTemplateTag {
|
|
return <JSDocTemplateTag>getJSDocTag(node, SyntaxKind.JSDocTemplateTag, /*checkParentVariableStatement*/ false);
|
|
}
|
|
|
|
export function getCorrespondingJSDocParameterTag(parameter: ParameterDeclaration): JSDocParameterTag {
|
|
if (parameter.name && parameter.name.kind === SyntaxKind.Identifier) {
|
|
// If it's a parameter, see if the parent has a jsdoc comment with an @param
|
|
// annotation.
|
|
const parameterName = (<Identifier>parameter.name).text;
|
|
|
|
const jsDocTags = getJSDocTags(parameter.parent, /*checkParentVariableStatement*/ true);
|
|
if (!jsDocTags) {
|
|
return undefined;
|
|
}
|
|
for (const tag of jsDocTags) {
|
|
if (tag.kind === SyntaxKind.JSDocParameterTag) {
|
|
const parameterTag = <JSDocParameterTag>tag;
|
|
if (parameterTag.parameterName.text === parameterName) {
|
|
return parameterTag;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function hasRestParameter(s: SignatureDeclaration): boolean {
|
|
return isRestParameter(lastOrUndefined(s.parameters));
|
|
}
|
|
|
|
export function hasDeclaredRestParameter(s: SignatureDeclaration): boolean {
|
|
return isDeclaredRestParam(lastOrUndefined(s.parameters));
|
|
}
|
|
|
|
export function isRestParameter(node: ParameterDeclaration) {
|
|
if (node && (node.flags & NodeFlags.JavaScriptFile)) {
|
|
if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType) {
|
|
return true;
|
|
}
|
|
|
|
const paramTag = getCorrespondingJSDocParameterTag(node);
|
|
if (paramTag && paramTag.typeExpression) {
|
|
return paramTag.typeExpression.type.kind === SyntaxKind.JSDocVariadicType;
|
|
}
|
|
}
|
|
return isDeclaredRestParam(node);
|
|
}
|
|
|
|
export function isDeclaredRestParam(node: ParameterDeclaration) {
|
|
return node && node.dotDotDotToken !== undefined;
|
|
}
|
|
|
|
export const enum AssignmentKind {
|
|
None, Definite, Compound
|
|
}
|
|
|
|
export function getAssignmentTargetKind(node: Node): AssignmentKind {
|
|
let parent = node.parent;
|
|
while (true) {
|
|
switch (parent.kind) {
|
|
case SyntaxKind.BinaryExpression:
|
|
const binaryOperator = (<BinaryExpression>parent).operatorToken.kind;
|
|
return isAssignmentOperator(binaryOperator) && (<BinaryExpression>parent).left === node ?
|
|
binaryOperator === SyntaxKind.EqualsToken ? AssignmentKind.Definite : AssignmentKind.Compound :
|
|
AssignmentKind.None;
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
case SyntaxKind.PostfixUnaryExpression:
|
|
const unaryOperator = (<PrefixUnaryExpression | PostfixUnaryExpression>parent).operator;
|
|
return unaryOperator === SyntaxKind.PlusPlusToken || unaryOperator === SyntaxKind.MinusMinusToken ? AssignmentKind.Compound : AssignmentKind.None;
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
return (<ForInStatement | ForOfStatement>parent).initializer === node ? AssignmentKind.Definite : AssignmentKind.None;
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
case SyntaxKind.SpreadElement:
|
|
node = parent;
|
|
break;
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
if ((<ShorthandPropertyAssignment>parent).name !== node) {
|
|
return AssignmentKind.None;
|
|
}
|
|
// Fall through
|
|
case SyntaxKind.PropertyAssignment:
|
|
node = parent.parent;
|
|
break;
|
|
default:
|
|
return AssignmentKind.None;
|
|
}
|
|
parent = node.parent;
|
|
}
|
|
}
|
|
|
|
// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
|
|
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
|
|
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'.
|
|
export function isAssignmentTarget(node: Node): boolean {
|
|
return getAssignmentTargetKind(node) !== AssignmentKind.None;
|
|
}
|
|
|
|
export function isNodeDescendantOf(node: Node, ancestor: Node): boolean {
|
|
while (node) {
|
|
if (node === ancestor) return true;
|
|
node = node.parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isInAmbientContext(node: Node): boolean {
|
|
while (node) {
|
|
if (hasModifier(node, ModifierFlags.Ambient) || (node.kind === SyntaxKind.SourceFile && (node as SourceFile).isDeclarationFile)) {
|
|
return true;
|
|
}
|
|
node = node.parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// True if the given identifier, string literal, or number literal is the name of a declaration node
|
|
export function isDeclarationName(name: Node): boolean {
|
|
if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.StringLiteral && name.kind !== SyntaxKind.NumericLiteral) {
|
|
return false;
|
|
}
|
|
|
|
const parent = name.parent;
|
|
if (parent.kind === SyntaxKind.ImportSpecifier || parent.kind === SyntaxKind.ExportSpecifier) {
|
|
if ((<ImportOrExportSpecifier>parent).propertyName) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (isDeclaration(parent)) {
|
|
return (<Declaration>parent).name === name;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function isLiteralComputedPropertyDeclarationName(node: Node) {
|
|
return (node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) &&
|
|
node.parent.kind === SyntaxKind.ComputedPropertyName &&
|
|
isDeclaration(node.parent.parent);
|
|
}
|
|
|
|
// Return true if the given identifier is classified as an IdentifierName
|
|
export function isIdentifierName(node: Identifier): boolean {
|
|
let parent = node.parent;
|
|
switch (parent.kind) {
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.EnumMember:
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
// Name in member declaration or property name in property access
|
|
return (<Declaration | PropertyAccessExpression>parent).name === node;
|
|
case SyntaxKind.QualifiedName:
|
|
// Name on right hand side of dot in a type query
|
|
if ((<QualifiedName>parent).right === node) {
|
|
while (parent.kind === SyntaxKind.QualifiedName) {
|
|
parent = parent.parent;
|
|
}
|
|
return parent.kind === SyntaxKind.TypeQuery;
|
|
}
|
|
return false;
|
|
case SyntaxKind.BindingElement:
|
|
case SyntaxKind.ImportSpecifier:
|
|
// Property name in binding element or import specifier
|
|
return (<BindingElement | ImportSpecifier>parent).propertyName === node;
|
|
case SyntaxKind.ExportSpecifier:
|
|
// Any name in an export specifier
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// An alias symbol is created by one of the following declarations:
|
|
// import <symbol> = ...
|
|
// import <symbol> from ...
|
|
// import * as <symbol> from ...
|
|
// import { x as <symbol> } from ...
|
|
// export { x as <symbol> } from ...
|
|
// export = <EntityNameExpression>
|
|
// export default <EntityNameExpression>
|
|
export function isAliasSymbolDeclaration(node: Node): boolean {
|
|
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
|
|
node.kind === SyntaxKind.NamespaceExportDeclaration ||
|
|
node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name ||
|
|
node.kind === SyntaxKind.NamespaceImport ||
|
|
node.kind === SyntaxKind.ImportSpecifier ||
|
|
node.kind === SyntaxKind.ExportSpecifier ||
|
|
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node);
|
|
}
|
|
|
|
export function exportAssignmentIsAlias(node: ExportAssignment): boolean {
|
|
return isEntityNameExpression(node.expression);
|
|
}
|
|
|
|
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration) {
|
|
const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
|
|
return heritageClause && heritageClause.types.length > 0 ? heritageClause.types[0] : undefined;
|
|
}
|
|
|
|
export function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration) {
|
|
const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ImplementsKeyword);
|
|
return heritageClause ? heritageClause.types : undefined;
|
|
}
|
|
|
|
export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration) {
|
|
const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
|
|
return heritageClause ? heritageClause.types : undefined;
|
|
}
|
|
|
|
export function getHeritageClause(clauses: NodeArray<HeritageClause>, kind: SyntaxKind) {
|
|
if (clauses) {
|
|
for (const clause of clauses) {
|
|
if (clause.token === kind) {
|
|
return clause;
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function tryResolveScriptReference(host: ScriptReferenceHost, sourceFile: SourceFile, reference: FileReference) {
|
|
if (!host.getCompilerOptions().noResolve) {
|
|
const referenceFileName = isRootedDiskPath(reference.fileName) ? reference.fileName : combinePaths(getDirectoryPath(sourceFile.fileName), reference.fileName);
|
|
return host.getSourceFile(referenceFileName);
|
|
}
|
|
}
|
|
|
|
export function getAncestor(node: Node, kind: SyntaxKind): Node {
|
|
while (node) {
|
|
if (node.kind === kind) {
|
|
return node;
|
|
}
|
|
node = node.parent;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function getFileReferenceFromReferencePath(comment: string, commentRange: CommentRange): ReferencePathMatchResult {
|
|
const simpleReferenceRegEx = /^\/\/\/\s*<reference\s+/gim;
|
|
const isNoDefaultLibRegEx = /^(\/\/\/\s*<reference\s+no-default-lib\s*=\s*)('|")(.+?)\2\s*\/>/gim;
|
|
if (simpleReferenceRegEx.test(comment)) {
|
|
if (isNoDefaultLibRegEx.test(comment)) {
|
|
return {
|
|
isNoDefaultLib: true
|
|
};
|
|
}
|
|
else {
|
|
const refMatchResult = fullTripleSlashReferencePathRegEx.exec(comment);
|
|
const refLibResult = !refMatchResult && fullTripleSlashReferenceTypeReferenceDirectiveRegEx.exec(comment);
|
|
if (refMatchResult || refLibResult) {
|
|
const start = commentRange.pos;
|
|
const end = commentRange.end;
|
|
return {
|
|
fileReference: {
|
|
pos: start,
|
|
end: end,
|
|
fileName: (refMatchResult || refLibResult)[3]
|
|
},
|
|
isNoDefaultLib: false,
|
|
isTypeReferenceDirective: !!refLibResult
|
|
};
|
|
}
|
|
|
|
return {
|
|
diagnosticMessage: Diagnostics.Invalid_reference_directive_syntax,
|
|
isNoDefaultLib: false
|
|
};
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function isKeyword(token: SyntaxKind): boolean {
|
|
return SyntaxKind.FirstKeyword <= token && token <= SyntaxKind.LastKeyword;
|
|
}
|
|
|
|
export function isTrivia(token: SyntaxKind) {
|
|
return SyntaxKind.FirstTriviaToken <= token && token <= SyntaxKind.LastTriviaToken;
|
|
}
|
|
|
|
export function isAsyncFunctionLike(node: Node): boolean {
|
|
return isFunctionLike(node) && hasModifier(node, ModifierFlags.Async) && !isAccessor(node);
|
|
}
|
|
|
|
export function isStringOrNumericLiteral(kind: SyntaxKind): boolean {
|
|
return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral;
|
|
}
|
|
|
|
/**
|
|
* A declaration has a dynamic name if both of the following are true:
|
|
* 1. The declaration has a computed property name
|
|
* 2. The computed name is *not* expressed as Symbol.<name>, where name
|
|
* is a property of the Symbol constructor that denotes a built in
|
|
* Symbol.
|
|
*/
|
|
export function hasDynamicName(declaration: Declaration): boolean {
|
|
return declaration.name && isDynamicName(declaration.name);
|
|
}
|
|
|
|
export function isDynamicName(name: DeclarationName): boolean {
|
|
return name.kind === SyntaxKind.ComputedPropertyName &&
|
|
!isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind) &&
|
|
!isWellKnownSymbolSyntactically((<ComputedPropertyName>name).expression);
|
|
}
|
|
|
|
/**
|
|
* Checks if the expression is of the form:
|
|
* Symbol.name
|
|
* where Symbol is literally the word "Symbol", and name is any identifierName
|
|
*/
|
|
export function isWellKnownSymbolSyntactically(node: Expression): boolean {
|
|
return isPropertyAccessExpression(node) && isESSymbolIdentifier(node.expression);
|
|
}
|
|
|
|
export function getPropertyNameForPropertyNameNode(name: DeclarationName): string {
|
|
if (name.kind === SyntaxKind.Identifier || name.kind === SyntaxKind.StringLiteral || name.kind === SyntaxKind.NumericLiteral || name.kind === SyntaxKind.Parameter) {
|
|
return (<Identifier | LiteralExpression>name).text;
|
|
}
|
|
if (name.kind === SyntaxKind.ComputedPropertyName) {
|
|
const nameExpression = (<ComputedPropertyName>name).expression;
|
|
if (isWellKnownSymbolSyntactically(nameExpression)) {
|
|
const rightHandSideName = (<PropertyAccessExpression>nameExpression).name.text;
|
|
return getPropertyNameForKnownSymbolName(rightHandSideName);
|
|
}
|
|
else if (nameExpression.kind === SyntaxKind.StringLiteral || nameExpression.kind === SyntaxKind.NumericLiteral) {
|
|
return (<LiteralExpression>nameExpression).text;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function getPropertyNameForKnownSymbolName(symbolName: string): string {
|
|
return "__@" + symbolName;
|
|
}
|
|
|
|
/**
|
|
* Includes the word "Symbol" with unicode escapes
|
|
*/
|
|
export function isESSymbolIdentifier(node: Node): boolean {
|
|
return node.kind === SyntaxKind.Identifier && (<Identifier>node).text === "Symbol";
|
|
}
|
|
|
|
export function isPushOrUnshiftIdentifier(node: Identifier) {
|
|
return node.text === "push" || node.text === "unshift";
|
|
}
|
|
|
|
export function isModifierKind(token: SyntaxKind): boolean {
|
|
switch (token) {
|
|
case SyntaxKind.AbstractKeyword:
|
|
case SyntaxKind.AsyncKeyword:
|
|
case SyntaxKind.ConstKeyword:
|
|
case SyntaxKind.DeclareKeyword:
|
|
case SyntaxKind.DefaultKeyword:
|
|
case SyntaxKind.ExportKeyword:
|
|
case SyntaxKind.PublicKeyword:
|
|
case SyntaxKind.PrivateKeyword:
|
|
case SyntaxKind.ProtectedKeyword:
|
|
case SyntaxKind.ReadonlyKeyword:
|
|
case SyntaxKind.StaticKeyword:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isParameterDeclaration(node: VariableLikeDeclaration) {
|
|
const root = getRootDeclaration(node);
|
|
return root.kind === SyntaxKind.Parameter;
|
|
}
|
|
|
|
export function getRootDeclaration(node: Node): Node {
|
|
while (node.kind === SyntaxKind.BindingElement) {
|
|
node = node.parent.parent;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
export function nodeStartsNewLexicalEnvironment(node: Node): boolean {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.Constructor
|
|
|| kind === SyntaxKind.FunctionExpression
|
|
|| kind === SyntaxKind.FunctionDeclaration
|
|
|| kind === SyntaxKind.ArrowFunction
|
|
|| kind === SyntaxKind.MethodDeclaration
|
|
|| kind === SyntaxKind.GetAccessor
|
|
|| kind === SyntaxKind.SetAccessor
|
|
|| kind === SyntaxKind.ModuleDeclaration
|
|
|| kind === SyntaxKind.SourceFile;
|
|
}
|
|
|
|
export function nodeIsSynthesized(node: TextRange): boolean {
|
|
return positionIsSynthesized(node.pos)
|
|
|| positionIsSynthesized(node.end);
|
|
}
|
|
|
|
export function getOriginalNode(node: Node): Node;
|
|
export function getOriginalNode<T extends Node>(node: Node, nodeTest: (node: Node) => node is T): T;
|
|
export function getOriginalNode(node: Node, nodeTest?: (node: Node) => boolean): Node {
|
|
if (node) {
|
|
while (node.original !== undefined) {
|
|
node = node.original;
|
|
}
|
|
}
|
|
|
|
return !nodeTest || nodeTest(node) ? node : undefined;
|
|
}
|
|
|
|
/**
|
|
* Gets a value indicating whether a node originated in the parse tree.
|
|
*
|
|
* @param node The node to test.
|
|
*/
|
|
export function isParseTreeNode(node: Node): boolean {
|
|
return (node.flags & NodeFlags.Synthesized) === 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the original parse tree node for a node.
|
|
*
|
|
* @param node The original node.
|
|
* @returns The original parse tree node if found; otherwise, undefined.
|
|
*/
|
|
export function getParseTreeNode(node: Node): Node;
|
|
|
|
/**
|
|
* Gets the original parse tree node for a node.
|
|
*
|
|
* @param node The original node.
|
|
* @param nodeTest A callback used to ensure the correct type of parse tree node is returned.
|
|
* @returns The original parse tree node if found; otherwise, undefined.
|
|
*/
|
|
export function getParseTreeNode<T extends Node>(node: Node, nodeTest?: (node: Node) => node is T): T;
|
|
export function getParseTreeNode(node: Node, nodeTest?: (node: Node) => boolean): Node {
|
|
if (isParseTreeNode(node)) {
|
|
return node;
|
|
}
|
|
|
|
node = getOriginalNode(node);
|
|
|
|
if (isParseTreeNode(node) && (!nodeTest || nodeTest(node))) {
|
|
return node;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function getOriginalSourceFiles(sourceFiles: SourceFile[]) {
|
|
const originalSourceFiles: SourceFile[] = [];
|
|
for (const sourceFile of sourceFiles) {
|
|
const originalSourceFile = getParseTreeNode(sourceFile, isSourceFile);
|
|
if (originalSourceFile) {
|
|
originalSourceFiles.push(originalSourceFile);
|
|
}
|
|
}
|
|
|
|
return originalSourceFiles;
|
|
}
|
|
|
|
export function getOriginalNodeId(node: Node) {
|
|
node = getOriginalNode(node);
|
|
return node ? getNodeId(node) : 0;
|
|
}
|
|
|
|
export const enum Associativity {
|
|
Left,
|
|
Right
|
|
}
|
|
|
|
export function getExpressionAssociativity(expression: Expression) {
|
|
const operator = getOperator(expression);
|
|
const hasArguments = expression.kind === SyntaxKind.NewExpression && (<NewExpression>expression).arguments !== undefined;
|
|
return getOperatorAssociativity(expression.kind, operator, hasArguments);
|
|
}
|
|
|
|
export function getOperatorAssociativity(kind: SyntaxKind, operator: SyntaxKind, hasArguments?: boolean) {
|
|
switch (kind) {
|
|
case SyntaxKind.NewExpression:
|
|
return hasArguments ? Associativity.Left : Associativity.Right;
|
|
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
case SyntaxKind.TypeOfExpression:
|
|
case SyntaxKind.VoidExpression:
|
|
case SyntaxKind.DeleteExpression:
|
|
case SyntaxKind.AwaitExpression:
|
|
case SyntaxKind.ConditionalExpression:
|
|
case SyntaxKind.YieldExpression:
|
|
return Associativity.Right;
|
|
|
|
case SyntaxKind.BinaryExpression:
|
|
switch (operator) {
|
|
case SyntaxKind.AsteriskAsteriskToken:
|
|
case SyntaxKind.EqualsToken:
|
|
case SyntaxKind.PlusEqualsToken:
|
|
case SyntaxKind.MinusEqualsToken:
|
|
case SyntaxKind.AsteriskAsteriskEqualsToken:
|
|
case SyntaxKind.AsteriskEqualsToken:
|
|
case SyntaxKind.SlashEqualsToken:
|
|
case SyntaxKind.PercentEqualsToken:
|
|
case SyntaxKind.LessThanLessThanEqualsToken:
|
|
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
|
|
case SyntaxKind.AmpersandEqualsToken:
|
|
case SyntaxKind.CaretEqualsToken:
|
|
case SyntaxKind.BarEqualsToken:
|
|
return Associativity.Right;
|
|
}
|
|
}
|
|
return Associativity.Left;
|
|
}
|
|
|
|
export function getExpressionPrecedence(expression: Expression) {
|
|
const operator = getOperator(expression);
|
|
const hasArguments = expression.kind === SyntaxKind.NewExpression && (<NewExpression>expression).arguments !== undefined;
|
|
return getOperatorPrecedence(expression.kind, operator, hasArguments);
|
|
}
|
|
|
|
export function getOperator(expression: Expression) {
|
|
if (expression.kind === SyntaxKind.BinaryExpression) {
|
|
return (<BinaryExpression>expression).operatorToken.kind;
|
|
}
|
|
else if (expression.kind === SyntaxKind.PrefixUnaryExpression || expression.kind === SyntaxKind.PostfixUnaryExpression) {
|
|
return (<PrefixUnaryExpression | PostfixUnaryExpression>expression).operator;
|
|
}
|
|
else {
|
|
return expression.kind;
|
|
}
|
|
}
|
|
|
|
export function getOperatorPrecedence(nodeKind: SyntaxKind, operatorKind: SyntaxKind, hasArguments?: boolean) {
|
|
switch (nodeKind) {
|
|
case SyntaxKind.ThisKeyword:
|
|
case SyntaxKind.SuperKeyword:
|
|
case SyntaxKind.Identifier:
|
|
case SyntaxKind.NullKeyword:
|
|
case SyntaxKind.TrueKeyword:
|
|
case SyntaxKind.FalseKeyword:
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.JsxElement:
|
|
case SyntaxKind.JsxSelfClosingElement:
|
|
case SyntaxKind.RegularExpressionLiteral:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
case SyntaxKind.TemplateExpression:
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
case SyntaxKind.OmittedExpression:
|
|
return 19;
|
|
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
return 18;
|
|
|
|
case SyntaxKind.NewExpression:
|
|
return hasArguments ? 18 : 17;
|
|
|
|
case SyntaxKind.CallExpression:
|
|
return 17;
|
|
|
|
case SyntaxKind.PostfixUnaryExpression:
|
|
return 16;
|
|
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
case SyntaxKind.TypeOfExpression:
|
|
case SyntaxKind.VoidExpression:
|
|
case SyntaxKind.DeleteExpression:
|
|
case SyntaxKind.AwaitExpression:
|
|
return 15;
|
|
|
|
case SyntaxKind.BinaryExpression:
|
|
switch (operatorKind) {
|
|
case SyntaxKind.ExclamationToken:
|
|
case SyntaxKind.TildeToken:
|
|
return 15;
|
|
|
|
case SyntaxKind.AsteriskAsteriskToken:
|
|
case SyntaxKind.AsteriskToken:
|
|
case SyntaxKind.SlashToken:
|
|
case SyntaxKind.PercentToken:
|
|
return 14;
|
|
|
|
case SyntaxKind.PlusToken:
|
|
case SyntaxKind.MinusToken:
|
|
return 13;
|
|
|
|
case SyntaxKind.LessThanLessThanToken:
|
|
case SyntaxKind.GreaterThanGreaterThanToken:
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
|
return 12;
|
|
|
|
case SyntaxKind.LessThanToken:
|
|
case SyntaxKind.LessThanEqualsToken:
|
|
case SyntaxKind.GreaterThanToken:
|
|
case SyntaxKind.GreaterThanEqualsToken:
|
|
case SyntaxKind.InKeyword:
|
|
case SyntaxKind.InstanceOfKeyword:
|
|
return 11;
|
|
|
|
case SyntaxKind.EqualsEqualsToken:
|
|
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
return 10;
|
|
|
|
case SyntaxKind.AmpersandToken:
|
|
return 9;
|
|
|
|
case SyntaxKind.CaretToken:
|
|
return 8;
|
|
|
|
case SyntaxKind.BarToken:
|
|
return 7;
|
|
|
|
case SyntaxKind.AmpersandAmpersandToken:
|
|
return 6;
|
|
|
|
case SyntaxKind.BarBarToken:
|
|
return 5;
|
|
|
|
case SyntaxKind.EqualsToken:
|
|
case SyntaxKind.PlusEqualsToken:
|
|
case SyntaxKind.MinusEqualsToken:
|
|
case SyntaxKind.AsteriskAsteriskEqualsToken:
|
|
case SyntaxKind.AsteriskEqualsToken:
|
|
case SyntaxKind.SlashEqualsToken:
|
|
case SyntaxKind.PercentEqualsToken:
|
|
case SyntaxKind.LessThanLessThanEqualsToken:
|
|
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
|
|
case SyntaxKind.AmpersandEqualsToken:
|
|
case SyntaxKind.CaretEqualsToken:
|
|
case SyntaxKind.BarEqualsToken:
|
|
return 3;
|
|
|
|
case SyntaxKind.CommaToken:
|
|
return 0;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
case SyntaxKind.ConditionalExpression:
|
|
return 4;
|
|
|
|
case SyntaxKind.YieldExpression:
|
|
return 2;
|
|
|
|
case SyntaxKind.SpreadElement:
|
|
return 1;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
export function createDiagnosticCollection(): DiagnosticCollection {
|
|
let nonFileDiagnostics: Diagnostic[] = [];
|
|
const fileDiagnostics = createMap<Diagnostic[]>();
|
|
|
|
let diagnosticsModified = false;
|
|
let modificationCount = 0;
|
|
|
|
return {
|
|
add,
|
|
getGlobalDiagnostics,
|
|
getDiagnostics,
|
|
getModificationCount,
|
|
reattachFileDiagnostics
|
|
};
|
|
|
|
function getModificationCount() {
|
|
return modificationCount;
|
|
}
|
|
|
|
function reattachFileDiagnostics(newFile: SourceFile): void {
|
|
if (!hasProperty(fileDiagnostics, newFile.fileName)) {
|
|
return;
|
|
}
|
|
|
|
for (const diagnostic of fileDiagnostics[newFile.fileName]) {
|
|
diagnostic.file = newFile;
|
|
}
|
|
}
|
|
|
|
function add(diagnostic: Diagnostic): void {
|
|
let diagnostics: Diagnostic[];
|
|
if (diagnostic.file) {
|
|
diagnostics = fileDiagnostics[diagnostic.file.fileName];
|
|
if (!diagnostics) {
|
|
diagnostics = [];
|
|
fileDiagnostics[diagnostic.file.fileName] = diagnostics;
|
|
}
|
|
}
|
|
else {
|
|
diagnostics = nonFileDiagnostics;
|
|
}
|
|
|
|
diagnostics.push(diagnostic);
|
|
diagnosticsModified = true;
|
|
modificationCount++;
|
|
}
|
|
|
|
function getGlobalDiagnostics(): Diagnostic[] {
|
|
sortAndDeduplicate();
|
|
return nonFileDiagnostics;
|
|
}
|
|
|
|
function getDiagnostics(fileName?: string): Diagnostic[] {
|
|
sortAndDeduplicate();
|
|
if (fileName) {
|
|
return fileDiagnostics[fileName] || [];
|
|
}
|
|
|
|
const allDiagnostics: Diagnostic[] = [];
|
|
function pushDiagnostic(d: Diagnostic) {
|
|
allDiagnostics.push(d);
|
|
}
|
|
|
|
forEach(nonFileDiagnostics, pushDiagnostic);
|
|
|
|
for (const key in fileDiagnostics) {
|
|
forEach(fileDiagnostics[key], pushDiagnostic);
|
|
}
|
|
|
|
return sortAndDeduplicateDiagnostics(allDiagnostics);
|
|
}
|
|
|
|
function sortAndDeduplicate() {
|
|
if (!diagnosticsModified) {
|
|
return;
|
|
}
|
|
|
|
diagnosticsModified = false;
|
|
nonFileDiagnostics = sortAndDeduplicateDiagnostics(nonFileDiagnostics);
|
|
|
|
for (const key in fileDiagnostics) {
|
|
fileDiagnostics[key] = sortAndDeduplicateDiagnostics(fileDiagnostics[key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This consists of the first 19 unprintable ASCII characters, canonical escapes, lineSeparator,
|
|
// paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in
|
|
// the language service. These characters should be escaped when printing, and if any characters are added,
|
|
// the map below must be updated. Note that this regexp *does not* include the 'delete' character.
|
|
// There is no reason for this other than that JSON.stringify does not handle it either.
|
|
const escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
|
|
const escapedCharsMap = createMap({
|
|
"\0": "\\0",
|
|
"\t": "\\t",
|
|
"\v": "\\v",
|
|
"\f": "\\f",
|
|
"\b": "\\b",
|
|
"\r": "\\r",
|
|
"\n": "\\n",
|
|
"\\": "\\\\",
|
|
"\"": "\\\"",
|
|
"\u2028": "\\u2028", // lineSeparator
|
|
"\u2029": "\\u2029", // paragraphSeparator
|
|
"\u0085": "\\u0085" // nextLine
|
|
});
|
|
|
|
|
|
/**
|
|
* Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2),
|
|
* but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine)
|
|
* Note that this doesn't actually wrap the input in double quotes.
|
|
*/
|
|
export function escapeString(s: string): string {
|
|
s = escapedCharsRegExp.test(s) ? s.replace(escapedCharsRegExp, getReplacement) : s;
|
|
|
|
return s;
|
|
|
|
function getReplacement(c: string) {
|
|
return escapedCharsMap[c] || get16BitUnicodeEscapeSequence(c.charCodeAt(0));
|
|
}
|
|
}
|
|
|
|
export function isIntrinsicJsxName(name: string) {
|
|
const ch = name.substr(0, 1);
|
|
return ch.toLowerCase() === ch;
|
|
}
|
|
|
|
function get16BitUnicodeEscapeSequence(charCode: number): string {
|
|
const hexCharCode = charCode.toString(16).toUpperCase();
|
|
const paddedHexCode = ("0000" + hexCharCode).slice(-4);
|
|
return "\\u" + paddedHexCode;
|
|
}
|
|
|
|
const nonAsciiCharacters = /[^\u0000-\u007F]/g;
|
|
export function escapeNonAsciiCharacters(s: string): string {
|
|
// Replace non-ASCII characters with '\uNNNN' escapes if any exist.
|
|
// Otherwise just return the original string.
|
|
return nonAsciiCharacters.test(s) ?
|
|
s.replace(nonAsciiCharacters, c => get16BitUnicodeEscapeSequence(c.charCodeAt(0))) :
|
|
s;
|
|
}
|
|
|
|
export interface EmitTextWriter {
|
|
write(s: string): void;
|
|
writeTextOfNode(text: string, node: Node): void;
|
|
writeLine(): void;
|
|
increaseIndent(): void;
|
|
decreaseIndent(): void;
|
|
getText(): string;
|
|
rawWrite(s: string): void;
|
|
writeLiteral(s: string): void;
|
|
getTextPos(): number;
|
|
getLine(): number;
|
|
getColumn(): number;
|
|
getIndent(): number;
|
|
isAtStartOfLine(): boolean;
|
|
reset(): void;
|
|
}
|
|
|
|
const indentStrings: string[] = ["", " "];
|
|
export function getIndentString(level: number) {
|
|
if (indentStrings[level] === undefined) {
|
|
indentStrings[level] = getIndentString(level - 1) + indentStrings[1];
|
|
}
|
|
return indentStrings[level];
|
|
}
|
|
|
|
export function getIndentSize() {
|
|
return indentStrings[1].length;
|
|
}
|
|
|
|
export function createTextWriter(newLine: String): EmitTextWriter {
|
|
let output: string;
|
|
let indent: number;
|
|
let lineStart: boolean;
|
|
let lineCount: number;
|
|
let linePos: number;
|
|
|
|
function write(s: string) {
|
|
if (s && s.length) {
|
|
if (lineStart) {
|
|
output += getIndentString(indent);
|
|
lineStart = false;
|
|
}
|
|
output += s;
|
|
}
|
|
}
|
|
|
|
function reset(): void {
|
|
output = "";
|
|
indent = 0;
|
|
lineStart = true;
|
|
lineCount = 0;
|
|
linePos = 0;
|
|
}
|
|
|
|
function rawWrite(s: string) {
|
|
if (s !== undefined) {
|
|
if (lineStart) {
|
|
lineStart = false;
|
|
}
|
|
output += s;
|
|
}
|
|
}
|
|
|
|
function writeLiteral(s: string) {
|
|
if (s && s.length) {
|
|
write(s);
|
|
const lineStartsOfS = computeLineStarts(s);
|
|
if (lineStartsOfS.length > 1) {
|
|
lineCount = lineCount + lineStartsOfS.length - 1;
|
|
linePos = output.length - s.length + lastOrUndefined(lineStartsOfS);
|
|
}
|
|
}
|
|
}
|
|
|
|
function writeLine() {
|
|
if (!lineStart) {
|
|
output += newLine;
|
|
lineCount++;
|
|
linePos = output.length;
|
|
lineStart = true;
|
|
}
|
|
}
|
|
|
|
function writeTextOfNode(text: string, node: Node) {
|
|
write(getTextOfNodeFromSourceText(text, node));
|
|
}
|
|
|
|
reset();
|
|
|
|
return {
|
|
write,
|
|
rawWrite,
|
|
writeTextOfNode,
|
|
writeLiteral,
|
|
writeLine,
|
|
increaseIndent: () => { indent++; },
|
|
decreaseIndent: () => { indent--; },
|
|
getIndent: () => indent,
|
|
getTextPos: () => output.length,
|
|
getLine: () => lineCount + 1,
|
|
getColumn: () => lineStart ? indent * getIndentSize() + 1 : output.length - linePos + 1,
|
|
getText: () => output,
|
|
isAtStartOfLine: () => lineStart,
|
|
reset
|
|
};
|
|
}
|
|
|
|
export function getResolvedExternalModuleName(host: EmitHost, file: SourceFile): string {
|
|
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName);
|
|
}
|
|
|
|
export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): string {
|
|
const file = resolver.getExternalModuleFileFromDeclaration(declaration);
|
|
if (!file || isDeclarationFile(file)) {
|
|
return undefined;
|
|
}
|
|
return getResolvedExternalModuleName(host, file);
|
|
}
|
|
|
|
/**
|
|
* Resolves a local path to a path which is absolute to the base of the emit
|
|
*/
|
|
export function getExternalModuleNameFromPath(host: EmitHost, fileName: string): string {
|
|
const getCanonicalFileName = (f: string) => host.getCanonicalFileName(f);
|
|
const dir = toPath(host.getCommonSourceDirectory(), host.getCurrentDirectory(), getCanonicalFileName);
|
|
const filePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory());
|
|
const relativePath = getRelativePathToDirectoryOrUrl(dir, filePath, dir, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
|
return removeFileExtension(relativePath);
|
|
}
|
|
|
|
export function getOwnEmitOutputFilePath(sourceFile: SourceFile, host: EmitHost, extension: string) {
|
|
const compilerOptions = host.getCompilerOptions();
|
|
let emitOutputFilePathWithoutExtension: string;
|
|
if (compilerOptions.outDir) {
|
|
emitOutputFilePathWithoutExtension = removeFileExtension(getSourceFilePathInNewDir(sourceFile, host, compilerOptions.outDir));
|
|
}
|
|
else {
|
|
emitOutputFilePathWithoutExtension = removeFileExtension(sourceFile.fileName);
|
|
}
|
|
|
|
return emitOutputFilePathWithoutExtension + extension;
|
|
}
|
|
|
|
export function getDeclarationEmitOutputFilePath(sourceFile: SourceFile, host: EmitHost) {
|
|
const options = host.getCompilerOptions();
|
|
const outputDir = options.declarationDir || options.outDir; // Prefer declaration folder if specified
|
|
|
|
const path = outputDir
|
|
? getSourceFilePathInNewDir(sourceFile, host, outputDir)
|
|
: sourceFile.fileName;
|
|
return removeFileExtension(path) + ".d.ts";
|
|
}
|
|
|
|
export interface EmitFileNames {
|
|
jsFilePath: string;
|
|
sourceMapFilePath: string;
|
|
declarationFilePath: string;
|
|
}
|
|
|
|
/**
|
|
* Gets the source files that are expected to have an emit output.
|
|
*
|
|
* Originally part of `forEachExpectedEmitFile`, this functionality was extracted to support
|
|
* transformations.
|
|
*
|
|
* @param host An EmitHost.
|
|
* @param targetSourceFile An optional target source file to emit.
|
|
*/
|
|
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile) {
|
|
const options = host.getCompilerOptions();
|
|
if (options.outFile || options.out) {
|
|
const moduleKind = getEmitModuleKind(options);
|
|
const moduleEmitEnabled = moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
|
|
const sourceFiles = getAllEmittableSourceFiles();
|
|
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
|
|
return filter(sourceFiles, moduleEmitEnabled ? isNonDeclarationFile : isBundleEmitNonExternalModule);
|
|
}
|
|
else {
|
|
const sourceFiles = targetSourceFile === undefined ? getAllEmittableSourceFiles() : [targetSourceFile];
|
|
return filterSourceFilesInDirectory(sourceFiles, file => host.isSourceFileFromExternalLibrary(file));
|
|
}
|
|
|
|
function getAllEmittableSourceFiles() {
|
|
return options.noEmitForJsFiles ? filter(host.getSourceFiles(), sourceFile => !isSourceFileJavaScript(sourceFile)) : host.getSourceFiles();
|
|
}
|
|
}
|
|
|
|
/** Don't call this for `--outFile`, just for `--outDir` or plain emit. */
|
|
export function filterSourceFilesInDirectory(sourceFiles: SourceFile[], isSourceFileFromExternalLibrary: (file: SourceFile) => boolean): SourceFile[] {
|
|
return filter(sourceFiles, file => shouldEmitInDirectory(file, isSourceFileFromExternalLibrary));
|
|
}
|
|
|
|
function isNonDeclarationFile(sourceFile: SourceFile) {
|
|
return !isDeclarationFile(sourceFile);
|
|
}
|
|
|
|
/**
|
|
* Whether a file should be emitted in a non-`--outFile` case.
|
|
* Don't emit if source file is a declaration file, or was located under node_modules
|
|
*/
|
|
function shouldEmitInDirectory(sourceFile: SourceFile, isSourceFileFromExternalLibrary: (file: SourceFile) => boolean): boolean {
|
|
return isNonDeclarationFile(sourceFile) && !isSourceFileFromExternalLibrary(sourceFile);
|
|
}
|
|
|
|
function isBundleEmitNonExternalModule(sourceFile: SourceFile) {
|
|
return isNonDeclarationFile(sourceFile) && !isExternalModule(sourceFile);
|
|
}
|
|
|
|
/**
|
|
* Iterates over each source file to emit. The source files are expected to have been
|
|
* transformed for use by the pretty printer.
|
|
*
|
|
* Originally part of `forEachExpectedEmitFile`, this functionality was extracted to support
|
|
* transformations.
|
|
*
|
|
* @param host An EmitHost.
|
|
* @param sourceFiles The transformed source files to emit.
|
|
* @param action The action to execute.
|
|
*/
|
|
export function forEachTransformedEmitFile(host: EmitHost, sourceFiles: SourceFile[],
|
|
action: (jsFilePath: string, sourceMapFilePath: string, declarationFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) => void,
|
|
emitOnlyDtsFiles?: boolean) {
|
|
const options = host.getCompilerOptions();
|
|
// Emit on each source file
|
|
if (options.outFile || options.out) {
|
|
onBundledEmit(sourceFiles);
|
|
}
|
|
else {
|
|
for (const sourceFile of sourceFiles) {
|
|
// Don't emit if source file is a declaration file, or was located under node_modules
|
|
if (!isDeclarationFile(sourceFile) && !host.isSourceFileFromExternalLibrary(sourceFile)) {
|
|
onSingleFileEmit(host, sourceFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onSingleFileEmit(host: EmitHost, sourceFile: SourceFile) {
|
|
// JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also.
|
|
// So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve.
|
|
// For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve
|
|
let extension = ".js";
|
|
if (options.jsx === JsxEmit.Preserve) {
|
|
if (isSourceFileJavaScript(sourceFile)) {
|
|
if (fileExtensionIs(sourceFile.fileName, ".jsx")) {
|
|
extension = ".jsx";
|
|
}
|
|
}
|
|
else if (sourceFile.languageVariant === LanguageVariant.JSX) {
|
|
// TypeScript source file preserving JSX syntax
|
|
extension = ".jsx";
|
|
}
|
|
}
|
|
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, extension);
|
|
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
|
|
const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (options.declaration || emitOnlyDtsFiles) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
|
|
action(jsFilePath, sourceMapFilePath, declarationFilePath, [sourceFile], /*isBundledEmit*/ false);
|
|
}
|
|
|
|
function onBundledEmit(sourceFiles: SourceFile[]) {
|
|
if (sourceFiles.length) {
|
|
const jsFilePath = options.outFile || options.out;
|
|
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
|
|
const declarationFilePath = options.declaration ? removeFileExtension(jsFilePath) + ".d.ts" : undefined;
|
|
action(jsFilePath, sourceMapFilePath, declarationFilePath, sourceFiles, /*isBundledEmit*/ true);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) {
|
|
return options.sourceMap ? jsFilePath + ".map" : undefined;
|
|
}
|
|
|
|
/**
|
|
* Iterates over the source files that are expected to have an emit output. This function
|
|
* is used by the legacy emitter and the declaration emitter and should not be used by
|
|
* the tree transforming emitter.
|
|
*
|
|
* @param host An EmitHost.
|
|
* @param action The action to execute.
|
|
* @param targetSourceFile An optional target source file to emit.
|
|
*/
|
|
export function forEachExpectedEmitFile(host: EmitHost,
|
|
action: (emitFileNames: EmitFileNames, sourceFiles: SourceFile[], isBundledEmit: boolean, emitOnlyDtsFiles: boolean) => void,
|
|
targetSourceFile?: SourceFile,
|
|
emitOnlyDtsFiles?: boolean) {
|
|
const options = host.getCompilerOptions();
|
|
// Emit on each source file
|
|
if (options.outFile || options.out) {
|
|
onBundledEmit(host);
|
|
}
|
|
else {
|
|
const sourceFiles = targetSourceFile === undefined ? getSourceFilesToEmit(host) : [targetSourceFile];
|
|
for (const sourceFile of sourceFiles) {
|
|
if (shouldEmitInDirectory(sourceFile, file => host.isSourceFileFromExternalLibrary(file))) {
|
|
onSingleFileEmit(host, sourceFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onSingleFileEmit(host: EmitHost, sourceFile: SourceFile) {
|
|
// JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also.
|
|
// So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve.
|
|
// For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve
|
|
let extension = ".js";
|
|
if (options.jsx === JsxEmit.Preserve) {
|
|
if (isSourceFileJavaScript(sourceFile)) {
|
|
if (fileExtensionIs(sourceFile.fileName, ".jsx")) {
|
|
extension = ".jsx";
|
|
}
|
|
}
|
|
else if (sourceFile.languageVariant === LanguageVariant.JSX) {
|
|
// TypeScript source file preserving JSX syntax
|
|
extension = ".jsx";
|
|
}
|
|
}
|
|
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, extension);
|
|
const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
|
|
const emitFileNames: EmitFileNames = {
|
|
jsFilePath,
|
|
sourceMapFilePath: getSourceMapFilePath(jsFilePath, options),
|
|
declarationFilePath
|
|
};
|
|
action(emitFileNames, [sourceFile], /*isBundledEmit*/false, emitOnlyDtsFiles);
|
|
}
|
|
|
|
function onBundledEmit(host: EmitHost) {
|
|
// Can emit only sources that are not declaration file and are either non module code or module with
|
|
// --module or --target es6 specified. Files included by searching under node_modules are also not emitted.
|
|
const bundledSources = filter(getSourceFilesToEmit(host),
|
|
sourceFile => !isDeclarationFile(sourceFile) &&
|
|
!host.isSourceFileFromExternalLibrary(sourceFile) &&
|
|
(!isExternalModule(sourceFile) ||
|
|
!!getEmitModuleKind(options)));
|
|
if (bundledSources.length) {
|
|
const jsFilePath = options.outFile || options.out;
|
|
const emitFileNames: EmitFileNames = {
|
|
jsFilePath,
|
|
sourceMapFilePath: getSourceMapFilePath(jsFilePath, options),
|
|
declarationFilePath: options.declaration ? removeFileExtension(jsFilePath) + ".d.ts" : undefined
|
|
};
|
|
action(emitFileNames, bundledSources, /*isBundledEmit*/true, emitOnlyDtsFiles);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getSourceFilePathInNewDir(sourceFile: SourceFile, host: EmitHost, newDirPath: string) {
|
|
let sourceFilePath = getNormalizedAbsolutePath(sourceFile.fileName, host.getCurrentDirectory());
|
|
const commonSourceDirectory = host.getCommonSourceDirectory();
|
|
const isSourceFileInCommonSourceDirectory = host.getCanonicalFileName(sourceFilePath).indexOf(host.getCanonicalFileName(commonSourceDirectory)) === 0;
|
|
sourceFilePath = isSourceFileInCommonSourceDirectory ? sourceFilePath.substring(commonSourceDirectory.length) : sourceFilePath;
|
|
return combinePaths(newDirPath, sourceFilePath);
|
|
}
|
|
|
|
export function writeFile(host: EmitHost, diagnostics: DiagnosticCollection, fileName: string, data: string, writeByteOrderMark: boolean, sourceFiles?: SourceFile[]) {
|
|
host.writeFile(fileName, data, writeByteOrderMark, hostErrorMessage => {
|
|
diagnostics.add(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, hostErrorMessage));
|
|
}, sourceFiles);
|
|
}
|
|
|
|
export function getLineOfLocalPosition(currentSourceFile: SourceFile, pos: number) {
|
|
return getLineAndCharacterOfPosition(currentSourceFile, pos).line;
|
|
}
|
|
|
|
export function getLineOfLocalPositionFromLineMap(lineMap: number[], pos: number) {
|
|
return computeLineAndCharacterOfPosition(lineMap, pos).line;
|
|
}
|
|
|
|
export function getFirstConstructorWithBody(node: ClassLikeDeclaration): ConstructorDeclaration {
|
|
return forEach(node.members, member => {
|
|
if (member.kind === SyntaxKind.Constructor && nodeIsPresent((<ConstructorDeclaration>member).body)) {
|
|
return <ConstructorDeclaration>member;
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Get the type annotaion for the value parameter. */
|
|
export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration): TypeNode {
|
|
if (accessor && accessor.parameters.length > 0) {
|
|
const hasThis = accessor.parameters.length === 2 && parameterIsThisKeyword(accessor.parameters[0]);
|
|
return accessor.parameters[hasThis ? 1 : 0].type;
|
|
}
|
|
}
|
|
|
|
export function getThisParameter(signature: SignatureDeclaration): ParameterDeclaration | undefined {
|
|
if (signature.parameters.length) {
|
|
const thisParameter = signature.parameters[0];
|
|
if (parameterIsThisKeyword(thisParameter)) {
|
|
return thisParameter;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function parameterIsThisKeyword(parameter: ParameterDeclaration): boolean {
|
|
return isThisIdentifier(parameter.name);
|
|
}
|
|
|
|
export function isThisIdentifier(node: Node | undefined): boolean {
|
|
return node && node.kind === SyntaxKind.Identifier && identifierIsThisKeyword(node as Identifier);
|
|
}
|
|
|
|
export function identifierIsThisKeyword(id: Identifier): boolean {
|
|
return id.originalKeywordKind === SyntaxKind.ThisKeyword;
|
|
}
|
|
|
|
export interface AllAccessorDeclarations {
|
|
firstAccessor: AccessorDeclaration;
|
|
secondAccessor: AccessorDeclaration;
|
|
getAccessor: AccessorDeclaration;
|
|
setAccessor: AccessorDeclaration;
|
|
}
|
|
|
|
export function getAllAccessorDeclarations(declarations: NodeArray<Declaration>, accessor: AccessorDeclaration): AllAccessorDeclarations {
|
|
let firstAccessor: AccessorDeclaration;
|
|
let secondAccessor: AccessorDeclaration;
|
|
let getAccessor: AccessorDeclaration;
|
|
let setAccessor: AccessorDeclaration;
|
|
if (hasDynamicName(accessor)) {
|
|
firstAccessor = accessor;
|
|
if (accessor.kind === SyntaxKind.GetAccessor) {
|
|
getAccessor = accessor;
|
|
}
|
|
else if (accessor.kind === SyntaxKind.SetAccessor) {
|
|
setAccessor = accessor;
|
|
}
|
|
else {
|
|
Debug.fail("Accessor has wrong kind");
|
|
}
|
|
}
|
|
else {
|
|
forEach(declarations, (member: Declaration) => {
|
|
if ((member.kind === SyntaxKind.GetAccessor || member.kind === SyntaxKind.SetAccessor)
|
|
&& hasModifier(member, ModifierFlags.Static) === hasModifier(accessor, ModifierFlags.Static)) {
|
|
const memberName = getPropertyNameForPropertyNameNode(member.name);
|
|
const accessorName = getPropertyNameForPropertyNameNode(accessor.name);
|
|
if (memberName === accessorName) {
|
|
if (!firstAccessor) {
|
|
firstAccessor = <AccessorDeclaration>member;
|
|
}
|
|
else if (!secondAccessor) {
|
|
secondAccessor = <AccessorDeclaration>member;
|
|
}
|
|
|
|
if (member.kind === SyntaxKind.GetAccessor && !getAccessor) {
|
|
getAccessor = <AccessorDeclaration>member;
|
|
}
|
|
|
|
if (member.kind === SyntaxKind.SetAccessor && !setAccessor) {
|
|
setAccessor = <AccessorDeclaration>member;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return {
|
|
firstAccessor,
|
|
secondAccessor,
|
|
getAccessor,
|
|
setAccessor
|
|
};
|
|
}
|
|
|
|
export function emitNewLineBeforeLeadingComments(lineMap: number[], writer: EmitTextWriter, node: TextRange, leadingComments: CommentRange[]) {
|
|
emitNewLineBeforeLeadingCommentsOfPosition(lineMap, writer, node.pos, leadingComments);
|
|
}
|
|
|
|
export function emitNewLineBeforeLeadingCommentsOfPosition(lineMap: number[], writer: EmitTextWriter, pos: number, leadingComments: CommentRange[]) {
|
|
// If the leading comments start on different line than the start of node, write new line
|
|
if (leadingComments && leadingComments.length && pos !== leadingComments[0].pos &&
|
|
getLineOfLocalPositionFromLineMap(lineMap, pos) !== getLineOfLocalPositionFromLineMap(lineMap, leadingComments[0].pos)) {
|
|
writer.writeLine();
|
|
}
|
|
}
|
|
|
|
export function emitNewLineBeforeLeadingCommentOfPosition(lineMap: number[], writer: EmitTextWriter, pos: number, commentPos: number) {
|
|
// If the leading comments start on different line than the start of node, write new line
|
|
if (pos !== commentPos &&
|
|
getLineOfLocalPositionFromLineMap(lineMap, pos) !== getLineOfLocalPositionFromLineMap(lineMap, commentPos)) {
|
|
writer.writeLine();
|
|
}
|
|
}
|
|
|
|
export function emitComments(text: string, lineMap: number[], writer: EmitTextWriter, comments: CommentRange[], leadingSeparator: boolean, trailingSeparator: boolean, newLine: string,
|
|
writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => void) {
|
|
if (comments && comments.length > 0) {
|
|
if (leadingSeparator) {
|
|
writer.write(" ");
|
|
}
|
|
|
|
let emitInterveningSeparator = false;
|
|
for (const comment of comments) {
|
|
if (emitInterveningSeparator) {
|
|
writer.write(" ");
|
|
emitInterveningSeparator = false;
|
|
}
|
|
|
|
writeComment(text, lineMap, writer, comment.pos, comment.end, newLine);
|
|
if (comment.hasTrailingNewLine) {
|
|
writer.writeLine();
|
|
}
|
|
else {
|
|
emitInterveningSeparator = true;
|
|
}
|
|
}
|
|
|
|
if (emitInterveningSeparator && trailingSeparator) {
|
|
writer.write(" ");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detached comment is a comment at the top of file or function body that is separated from
|
|
* the next statement by space.
|
|
*/
|
|
export function emitDetachedComments(text: string, lineMap: number[], writer: EmitTextWriter,
|
|
writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => void,
|
|
node: TextRange, newLine: string, removeComments: boolean) {
|
|
let leadingComments: CommentRange[];
|
|
let currentDetachedCommentInfo: { nodePos: number, detachedCommentEndPos: number };
|
|
if (removeComments) {
|
|
// removeComments is true, only reserve pinned comment at the top of file
|
|
// For example:
|
|
// /*! Pinned Comment */
|
|
//
|
|
// var x = 10;
|
|
if (node.pos === 0) {
|
|
leadingComments = filter(getLeadingCommentRanges(text, node.pos), isPinnedComment);
|
|
}
|
|
}
|
|
else {
|
|
// removeComments is false, just get detached as normal and bypass the process to filter comment
|
|
leadingComments = getLeadingCommentRanges(text, node.pos);
|
|
}
|
|
|
|
if (leadingComments) {
|
|
const detachedComments: CommentRange[] = [];
|
|
let lastComment: CommentRange;
|
|
|
|
for (const comment of leadingComments) {
|
|
if (lastComment) {
|
|
const lastCommentLine = getLineOfLocalPositionFromLineMap(lineMap, lastComment.end);
|
|
const commentLine = getLineOfLocalPositionFromLineMap(lineMap, comment.pos);
|
|
|
|
if (commentLine >= lastCommentLine + 2) {
|
|
// There was a blank line between the last comment and this comment. This
|
|
// comment is not part of the copyright comments. Return what we have so
|
|
// far.
|
|
break;
|
|
}
|
|
}
|
|
|
|
detachedComments.push(comment);
|
|
lastComment = comment;
|
|
}
|
|
|
|
if (detachedComments.length) {
|
|
// All comments look like they could have been part of the copyright header. Make
|
|
// sure there is at least one blank line between it and the node. If not, it's not
|
|
// a copyright header.
|
|
const lastCommentLine = getLineOfLocalPositionFromLineMap(lineMap, lastOrUndefined(detachedComments).end);
|
|
const nodeLine = getLineOfLocalPositionFromLineMap(lineMap, skipTrivia(text, node.pos));
|
|
if (nodeLine >= lastCommentLine + 2) {
|
|
// Valid detachedComments
|
|
emitNewLineBeforeLeadingComments(lineMap, writer, node, leadingComments);
|
|
emitComments(text, lineMap, writer, detachedComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment);
|
|
currentDetachedCommentInfo = { nodePos: node.pos, detachedCommentEndPos: lastOrUndefined(detachedComments).end };
|
|
}
|
|
}
|
|
}
|
|
|
|
return currentDetachedCommentInfo;
|
|
|
|
function isPinnedComment(comment: CommentRange) {
|
|
return text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk &&
|
|
text.charCodeAt(comment.pos + 2) === CharacterCodes.exclamation;
|
|
}
|
|
|
|
}
|
|
|
|
export function writeCommentRange(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) {
|
|
if (text.charCodeAt(commentPos + 1) === CharacterCodes.asterisk) {
|
|
const firstCommentLineAndCharacter = computeLineAndCharacterOfPosition(lineMap, commentPos);
|
|
const lineCount = lineMap.length;
|
|
let firstCommentLineIndent: number;
|
|
for (let pos = commentPos, currentLine = firstCommentLineAndCharacter.line; pos < commentEnd; currentLine++) {
|
|
const nextLineStart = (currentLine + 1) === lineCount
|
|
? text.length + 1
|
|
: lineMap[currentLine + 1];
|
|
|
|
if (pos !== commentPos) {
|
|
// If we are not emitting first line, we need to write the spaces to adjust the alignment
|
|
if (firstCommentLineIndent === undefined) {
|
|
firstCommentLineIndent = calculateIndent(text, lineMap[firstCommentLineAndCharacter.line], commentPos);
|
|
}
|
|
|
|
// These are number of spaces writer is going to write at current indent
|
|
const currentWriterIndentSpacing = writer.getIndent() * getIndentSize();
|
|
|
|
// Number of spaces we want to be writing
|
|
// eg: Assume writer indent
|
|
// module m {
|
|
// /* starts at character 9 this is line 1
|
|
// * starts at character pos 4 line --1 = 8 - 8 + 3
|
|
// More left indented comment */ --2 = 8 - 8 + 2
|
|
// class c { }
|
|
// }
|
|
// module m {
|
|
// /* this is line 1 -- Assume current writer indent 8
|
|
// * line --3 = 8 - 4 + 5
|
|
// More right indented comment */ --4 = 8 - 4 + 11
|
|
// class c { }
|
|
// }
|
|
const spacesToEmit = currentWriterIndentSpacing - firstCommentLineIndent + calculateIndent(text, pos, nextLineStart);
|
|
if (spacesToEmit > 0) {
|
|
let numberOfSingleSpacesToEmit = spacesToEmit % getIndentSize();
|
|
const indentSizeSpaceString = getIndentString((spacesToEmit - numberOfSingleSpacesToEmit) / getIndentSize());
|
|
|
|
// Write indent size string ( in eg 1: = "", 2: "" , 3: string with 8 spaces 4: string with 12 spaces
|
|
writer.rawWrite(indentSizeSpaceString);
|
|
|
|
// Emit the single spaces (in eg: 1: 3 spaces, 2: 2 spaces, 3: 1 space, 4: 3 spaces)
|
|
while (numberOfSingleSpacesToEmit) {
|
|
writer.rawWrite(" ");
|
|
numberOfSingleSpacesToEmit--;
|
|
}
|
|
}
|
|
else {
|
|
// No spaces to emit write empty string
|
|
writer.rawWrite("");
|
|
}
|
|
}
|
|
|
|
// Write the comment line text
|
|
writeTrimmedCurrentLine(text, commentEnd, writer, newLine, pos, nextLineStart);
|
|
|
|
pos = nextLineStart;
|
|
}
|
|
}
|
|
else {
|
|
// Single line comment of style //....
|
|
writer.write(text.substring(commentPos, commentEnd));
|
|
}
|
|
}
|
|
|
|
function writeTrimmedCurrentLine(text: string, commentEnd: number, writer: EmitTextWriter, newLine: string, pos: number, nextLineStart: number) {
|
|
const end = Math.min(commentEnd, nextLineStart - 1);
|
|
const currentLineText = text.substring(pos, end).replace(/^\s+|\s+$/g, "");
|
|
if (currentLineText) {
|
|
// trimmed forward and ending spaces text
|
|
writer.write(currentLineText);
|
|
if (end !== commentEnd) {
|
|
writer.writeLine();
|
|
}
|
|
}
|
|
else {
|
|
// Empty string - make sure we write empty line
|
|
writer.writeLiteral(newLine);
|
|
}
|
|
}
|
|
|
|
function calculateIndent(text: string, pos: number, end: number) {
|
|
let currentLineIndent = 0;
|
|
for (; pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++) {
|
|
if (text.charCodeAt(pos) === CharacterCodes.tab) {
|
|
// Tabs = TabSize = indent size and go to next tabStop
|
|
currentLineIndent += getIndentSize() - (currentLineIndent % getIndentSize());
|
|
}
|
|
else {
|
|
// Single space
|
|
currentLineIndent++;
|
|
}
|
|
}
|
|
|
|
return currentLineIndent;
|
|
}
|
|
|
|
export function hasModifiers(node: Node) {
|
|
return getModifierFlags(node) !== ModifierFlags.None;
|
|
}
|
|
|
|
export function hasModifier(node: Node, flags: ModifierFlags) {
|
|
return (getModifierFlags(node) & flags) !== 0;
|
|
}
|
|
|
|
export function getModifierFlags(node: Node): ModifierFlags {
|
|
if (node.modifierFlagsCache & ModifierFlags.HasComputedFlags) {
|
|
return node.modifierFlagsCache & ~ModifierFlags.HasComputedFlags;
|
|
}
|
|
|
|
let flags = ModifierFlags.None;
|
|
if (node.modifiers) {
|
|
for (const modifier of node.modifiers) {
|
|
flags |= modifierToFlag(modifier.kind);
|
|
}
|
|
}
|
|
|
|
if (node.flags & NodeFlags.NestedNamespace || (node.kind === SyntaxKind.Identifier && (<Identifier>node).isInJSDocNamespace)) {
|
|
flags |= ModifierFlags.Export;
|
|
}
|
|
|
|
node.modifierFlagsCache = flags | ModifierFlags.HasComputedFlags;
|
|
return flags;
|
|
}
|
|
|
|
export function modifierToFlag(token: SyntaxKind): ModifierFlags {
|
|
switch (token) {
|
|
case SyntaxKind.StaticKeyword: return ModifierFlags.Static;
|
|
case SyntaxKind.PublicKeyword: return ModifierFlags.Public;
|
|
case SyntaxKind.ProtectedKeyword: return ModifierFlags.Protected;
|
|
case SyntaxKind.PrivateKeyword: return ModifierFlags.Private;
|
|
case SyntaxKind.AbstractKeyword: return ModifierFlags.Abstract;
|
|
case SyntaxKind.ExportKeyword: return ModifierFlags.Export;
|
|
case SyntaxKind.DeclareKeyword: return ModifierFlags.Ambient;
|
|
case SyntaxKind.ConstKeyword: return ModifierFlags.Const;
|
|
case SyntaxKind.DefaultKeyword: return ModifierFlags.Default;
|
|
case SyntaxKind.AsyncKeyword: return ModifierFlags.Async;
|
|
case SyntaxKind.ReadonlyKeyword: return ModifierFlags.Readonly;
|
|
}
|
|
return ModifierFlags.None;
|
|
}
|
|
|
|
export function isLogicalOperator(token: SyntaxKind): boolean {
|
|
return token === SyntaxKind.BarBarToken
|
|
|| token === SyntaxKind.AmpersandAmpersandToken
|
|
|| token === SyntaxKind.ExclamationToken;
|
|
}
|
|
|
|
export function isAssignmentOperator(token: SyntaxKind): boolean {
|
|
return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment;
|
|
}
|
|
|
|
/** Get `C` given `N` if `N` is in the position `class C extends N` where `N` is an ExpressionWithTypeArguments. */
|
|
export function tryGetClassExtendingExpressionWithTypeArguments(node: Node): ClassLikeDeclaration | undefined {
|
|
if (node.kind === SyntaxKind.ExpressionWithTypeArguments &&
|
|
(<HeritageClause>node.parent).token === SyntaxKind.ExtendsKeyword &&
|
|
isClassLike(node.parent.parent)) {
|
|
return node.parent.parent;
|
|
}
|
|
}
|
|
|
|
export function isAssignmentExpression(node: Node): node is AssignmentExpression {
|
|
return isBinaryExpression(node)
|
|
&& isAssignmentOperator(node.operatorToken.kind)
|
|
&& isLeftHandSideExpression(node.left);
|
|
}
|
|
|
|
export function isDestructuringAssignment(node: Node): node is DestructuringAssignment {
|
|
if (isBinaryExpression(node)) {
|
|
if (node.operatorToken.kind === SyntaxKind.EqualsToken) {
|
|
const kind = node.left.kind;
|
|
return kind === SyntaxKind.ObjectLiteralExpression
|
|
|| kind === SyntaxKind.ArrayLiteralExpression;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Returns false if this heritage clause element's expression contains something unsupported
|
|
// (i.e. not a name or dotted name).
|
|
export function isSupportedExpressionWithTypeArguments(node: ExpressionWithTypeArguments): boolean {
|
|
return isSupportedExpressionWithTypeArgumentsRest(node.expression);
|
|
}
|
|
|
|
function isSupportedExpressionWithTypeArgumentsRest(node: Expression): boolean {
|
|
if (node.kind === SyntaxKind.Identifier) {
|
|
return true;
|
|
}
|
|
else if (isPropertyAccessExpression(node)) {
|
|
return isSupportedExpressionWithTypeArgumentsRest(node.expression);
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node): boolean {
|
|
return tryGetClassExtendingExpressionWithTypeArguments(node) !== undefined;
|
|
}
|
|
|
|
export function isEntityNameExpression(node: Expression): node is EntityNameExpression {
|
|
return node.kind === SyntaxKind.Identifier ||
|
|
node.kind === SyntaxKind.PropertyAccessExpression && isEntityNameExpression((<PropertyAccessExpression>node).expression);
|
|
}
|
|
|
|
export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) {
|
|
return (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node) ||
|
|
(node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node);
|
|
}
|
|
|
|
export function isEmptyObjectLiteralOrArrayLiteral(expression: Node): boolean {
|
|
const kind = expression.kind;
|
|
if (kind === SyntaxKind.ObjectLiteralExpression) {
|
|
return (<ObjectLiteralExpression>expression).properties.length === 0;
|
|
}
|
|
if (kind === SyntaxKind.ArrayLiteralExpression) {
|
|
return (<ArrayLiteralExpression>expression).elements.length === 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function getLocalSymbolForExportDefault(symbol: Symbol) {
|
|
return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
|
|
}
|
|
|
|
/** Return ".ts", ".d.ts", or ".tsx", if that is the extension. */
|
|
export function tryExtractTypeScriptExtension(fileName: string): string | undefined {
|
|
return find(supportedTypescriptExtensionsForExtractExtension, extension => fileExtensionIs(fileName, extension));
|
|
}
|
|
/**
|
|
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
|
|
* representing the UTF-8 encoding of the character, and return the expanded char code list.
|
|
*/
|
|
function getExpandedCharCodes(input: string): number[] {
|
|
const output: number[] = [];
|
|
const length = input.length;
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
const charCode = input.charCodeAt(i);
|
|
|
|
// handel utf8
|
|
if (charCode < 0x80) {
|
|
output.push(charCode);
|
|
}
|
|
else if (charCode < 0x800) {
|
|
output.push((charCode >> 6) | 0B11000000);
|
|
output.push((charCode & 0B00111111) | 0B10000000);
|
|
}
|
|
else if (charCode < 0x10000) {
|
|
output.push((charCode >> 12) | 0B11100000);
|
|
output.push(((charCode >> 6) & 0B00111111) | 0B10000000);
|
|
output.push((charCode & 0B00111111) | 0B10000000);
|
|
}
|
|
else if (charCode < 0x20000) {
|
|
output.push((charCode >> 18) | 0B11110000);
|
|
output.push(((charCode >> 12) & 0B00111111) | 0B10000000);
|
|
output.push(((charCode >> 6) & 0B00111111) | 0B10000000);
|
|
output.push((charCode & 0B00111111) | 0B10000000);
|
|
}
|
|
else {
|
|
Debug.assert(false, "Unexpected code point");
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Serialize an object graph into a JSON string. This is intended only for use on an acyclic graph
|
|
* as the fallback implementation does not check for circular references by default.
|
|
*/
|
|
export const stringify: (value: any) => string = typeof JSON !== "undefined" && JSON.stringify
|
|
? JSON.stringify
|
|
: stringifyFallback;
|
|
|
|
/**
|
|
* Serialize an object graph into a JSON string.
|
|
*/
|
|
function stringifyFallback(value: any): string {
|
|
// JSON.stringify returns `undefined` here, instead of the string "undefined".
|
|
return value === undefined ? undefined : stringifyValue(value);
|
|
}
|
|
|
|
function stringifyValue(value: any): string {
|
|
return typeof value === "string" ? `"${escapeString(value)}"`
|
|
: typeof value === "number" ? isFinite(value) ? String(value) : "null"
|
|
: typeof value === "boolean" ? value ? "true" : "false"
|
|
: typeof value === "object" && value ? isArray(value) ? cycleCheck(stringifyArray, value) : cycleCheck(stringifyObject, value)
|
|
: /*fallback*/ "null";
|
|
}
|
|
|
|
function cycleCheck(cb: (value: any) => string, value: any) {
|
|
Debug.assert(!value.hasOwnProperty("__cycle"), "Converting circular structure to JSON");
|
|
value.__cycle = true;
|
|
const result = cb(value);
|
|
delete value.__cycle;
|
|
return result;
|
|
}
|
|
|
|
function stringifyArray(value: any) {
|
|
return `[${reduceLeft(value, stringifyElement, "")}]`;
|
|
}
|
|
|
|
function stringifyElement(memo: string, value: any) {
|
|
return (memo ? memo + "," : memo) + stringifyValue(value);
|
|
}
|
|
|
|
function stringifyObject(value: any) {
|
|
return `{${reduceOwnProperties(value, stringifyProperty, "")}}`;
|
|
}
|
|
|
|
function stringifyProperty(memo: string, value: any, key: string) {
|
|
return value === undefined || typeof value === "function" || key === "__cycle" ? memo
|
|
: (memo ? memo + "," : memo) + `"${escapeString(key)}":${stringifyValue(value)}`;
|
|
}
|
|
|
|
const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
|
|
/**
|
|
* Converts a string to a base-64 encoded ASCII string.
|
|
*/
|
|
export function convertToBase64(input: string): string {
|
|
let result = "";
|
|
const charCodes = getExpandedCharCodes(input);
|
|
let i = 0;
|
|
const length = charCodes.length;
|
|
let byte1: number, byte2: number, byte3: number, byte4: number;
|
|
|
|
while (i < length) {
|
|
// Convert every 6-bits in the input 3 character points
|
|
// into a base64 digit
|
|
byte1 = charCodes[i] >> 2;
|
|
byte2 = (charCodes[i] & 0B00000011) << 4 | charCodes[i + 1] >> 4;
|
|
byte3 = (charCodes[i + 1] & 0B00001111) << 2 | charCodes[i + 2] >> 6;
|
|
byte4 = charCodes[i + 2] & 0B00111111;
|
|
|
|
// We are out of characters in the input, set the extra
|
|
// digits to 64 (padding character).
|
|
if (i + 1 >= length) {
|
|
byte3 = byte4 = 64;
|
|
}
|
|
else if (i + 2 >= length) {
|
|
byte4 = 64;
|
|
}
|
|
|
|
// Write to the output
|
|
result += base64Digits.charAt(byte1) + base64Digits.charAt(byte2) + base64Digits.charAt(byte3) + base64Digits.charAt(byte4);
|
|
|
|
i += 3;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const carriageReturnLineFeed = "\r\n";
|
|
const lineFeed = "\n";
|
|
export function getNewLineCharacter(options: CompilerOptions): string {
|
|
if (options.newLine === NewLineKind.CarriageReturnLineFeed) {
|
|
return carriageReturnLineFeed;
|
|
}
|
|
else if (options.newLine === NewLineKind.LineFeed) {
|
|
return lineFeed;
|
|
}
|
|
else if (sys) {
|
|
return sys.newLine;
|
|
}
|
|
return carriageReturnLineFeed;
|
|
}
|
|
|
|
/**
|
|
* Tests whether a node and its subtree is simple enough to have its position
|
|
* information ignored when emitting source maps in a destructuring assignment.
|
|
*
|
|
* @param node The expression to test.
|
|
*/
|
|
export function isSimpleExpression(node: Expression): boolean {
|
|
return isSimpleExpressionWorker(node, 0);
|
|
}
|
|
|
|
function isSimpleExpressionWorker(node: Expression, depth: number): boolean {
|
|
if (depth <= 5) {
|
|
const kind = node.kind;
|
|
if (kind === SyntaxKind.StringLiteral
|
|
|| kind === SyntaxKind.NumericLiteral
|
|
|| kind === SyntaxKind.RegularExpressionLiteral
|
|
|| kind === SyntaxKind.NoSubstitutionTemplateLiteral
|
|
|| kind === SyntaxKind.Identifier
|
|
|| kind === SyntaxKind.ThisKeyword
|
|
|| kind === SyntaxKind.SuperKeyword
|
|
|| kind === SyntaxKind.TrueKeyword
|
|
|| kind === SyntaxKind.FalseKeyword
|
|
|| kind === SyntaxKind.NullKeyword) {
|
|
return true;
|
|
}
|
|
else if (kind === SyntaxKind.PropertyAccessExpression) {
|
|
return isSimpleExpressionWorker((<PropertyAccessExpression>node).expression, depth + 1);
|
|
}
|
|
else if (kind === SyntaxKind.ElementAccessExpression) {
|
|
return isSimpleExpressionWorker((<ElementAccessExpression>node).expression, depth + 1)
|
|
&& isSimpleExpressionWorker((<ElementAccessExpression>node).argumentExpression, depth + 1);
|
|
}
|
|
else if (kind === SyntaxKind.PrefixUnaryExpression
|
|
|| kind === SyntaxKind.PostfixUnaryExpression) {
|
|
return isSimpleExpressionWorker((<PrefixUnaryExpression | PostfixUnaryExpression>node).operand, depth + 1);
|
|
}
|
|
else if (kind === SyntaxKind.BinaryExpression) {
|
|
return (<BinaryExpression>node).operatorToken.kind !== SyntaxKind.AsteriskAsteriskToken
|
|
&& isSimpleExpressionWorker((<BinaryExpression>node).left, depth + 1)
|
|
&& isSimpleExpressionWorker((<BinaryExpression>node).right, depth + 1);
|
|
}
|
|
else if (kind === SyntaxKind.ConditionalExpression) {
|
|
return isSimpleExpressionWorker((<ConditionalExpression>node).condition, depth + 1)
|
|
&& isSimpleExpressionWorker((<ConditionalExpression>node).whenTrue, depth + 1)
|
|
&& isSimpleExpressionWorker((<ConditionalExpression>node).whenFalse, depth + 1);
|
|
}
|
|
else if (kind === SyntaxKind.VoidExpression
|
|
|| kind === SyntaxKind.TypeOfExpression
|
|
|| kind === SyntaxKind.DeleteExpression) {
|
|
return isSimpleExpressionWorker((<VoidExpression | TypeOfExpression | DeleteExpression>node).expression, depth + 1);
|
|
}
|
|
else if (kind === SyntaxKind.ArrayLiteralExpression) {
|
|
return (<ArrayLiteralExpression>node).elements.length === 0;
|
|
}
|
|
else if (kind === SyntaxKind.ObjectLiteralExpression) {
|
|
return (<ObjectLiteralExpression>node).properties.length === 0;
|
|
}
|
|
else if (kind === SyntaxKind.CallExpression) {
|
|
if (!isSimpleExpressionWorker((<CallExpression>node).expression, depth + 1)) {
|
|
return false;
|
|
}
|
|
|
|
for (const argument of (<CallExpression>node).arguments) {
|
|
if (!isSimpleExpressionWorker(argument, depth + 1)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const syntaxKindCache = createMap<string>();
|
|
|
|
export function formatSyntaxKind(kind: SyntaxKind): string {
|
|
const syntaxKindEnum = (<any>ts).SyntaxKind;
|
|
if (syntaxKindEnum) {
|
|
if (syntaxKindCache[kind]) {
|
|
return syntaxKindCache[kind];
|
|
}
|
|
|
|
for (const name in syntaxKindEnum) {
|
|
if (syntaxKindEnum[name] === kind) {
|
|
return syntaxKindCache[kind] = kind.toString() + " (" + name + ")";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return kind.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Increases (or decreases) a position by the provided amount.
|
|
*
|
|
* @param pos The position.
|
|
* @param value The delta.
|
|
*/
|
|
export function movePos(pos: number, value: number) {
|
|
return positionIsSynthesized(pos) ? -1 : pos + value;
|
|
}
|
|
|
|
/**
|
|
* Creates a new TextRange from the provided pos and end.
|
|
*
|
|
* @param pos The start position.
|
|
* @param end The end position.
|
|
*/
|
|
export function createRange(pos: number, end: number): TextRange {
|
|
return { pos, end };
|
|
}
|
|
|
|
/**
|
|
* Creates a new TextRange from a provided range with a new end position.
|
|
*
|
|
* @param range A TextRange.
|
|
* @param end The new end position.
|
|
*/
|
|
export function moveRangeEnd(range: TextRange, end: number): TextRange {
|
|
return createRange(range.pos, end);
|
|
}
|
|
|
|
/**
|
|
* Creates a new TextRange from a provided range with a new start position.
|
|
*
|
|
* @param range A TextRange.
|
|
* @param pos The new Start position.
|
|
*/
|
|
export function moveRangePos(range: TextRange, pos: number): TextRange {
|
|
return createRange(pos, range.end);
|
|
}
|
|
|
|
/**
|
|
* Moves the start position of a range past any decorators.
|
|
*/
|
|
export function moveRangePastDecorators(node: Node): TextRange {
|
|
return node.decorators && node.decorators.length > 0
|
|
? moveRangePos(node, node.decorators.end)
|
|
: node;
|
|
}
|
|
|
|
/**
|
|
* Moves the start position of a range past any decorators or modifiers.
|
|
*/
|
|
export function moveRangePastModifiers(node: Node): TextRange {
|
|
return node.modifiers && node.modifiers.length > 0
|
|
? moveRangePos(node, node.modifiers.end)
|
|
: moveRangePastDecorators(node);
|
|
}
|
|
|
|
/**
|
|
* Determines whether a TextRange has the same start and end positions.
|
|
*
|
|
* @param range A TextRange.
|
|
*/
|
|
export function isCollapsedRange(range: TextRange) {
|
|
return range.pos === range.end;
|
|
}
|
|
|
|
/**
|
|
* Creates a new TextRange from a provided range with its end position collapsed to its
|
|
* start position.
|
|
*
|
|
* @param range A TextRange.
|
|
*/
|
|
export function collapseRangeToStart(range: TextRange): TextRange {
|
|
return isCollapsedRange(range) ? range : moveRangeEnd(range, range.pos);
|
|
}
|
|
|
|
/**
|
|
* Creates a new TextRange from a provided range with its start position collapsed to its
|
|
* end position.
|
|
*
|
|
* @param range A TextRange.
|
|
*/
|
|
export function collapseRangeToEnd(range: TextRange): TextRange {
|
|
return isCollapsedRange(range) ? range : moveRangePos(range, range.end);
|
|
}
|
|
|
|
/**
|
|
* Creates a new TextRange for a token at the provides start position.
|
|
*
|
|
* @param pos The start position.
|
|
* @param token The token.
|
|
*/
|
|
export function createTokenRange(pos: number, token: SyntaxKind): TextRange {
|
|
return createRange(pos, pos + tokenToString(token).length);
|
|
}
|
|
|
|
export function rangeIsOnSingleLine(range: TextRange, sourceFile: SourceFile) {
|
|
return rangeStartIsOnSameLineAsRangeEnd(range, range, sourceFile);
|
|
}
|
|
|
|
export function rangeStartPositionsAreOnSameLine(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
|
return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile), getStartPositionOfRange(range2, sourceFile), sourceFile);
|
|
}
|
|
|
|
export function rangeEndPositionsAreOnSameLine(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
|
return positionsAreOnSameLine(range1.end, range2.end, sourceFile);
|
|
}
|
|
|
|
export function rangeStartIsOnSameLineAsRangeEnd(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
|
return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile), range2.end, sourceFile);
|
|
}
|
|
|
|
export function rangeEndIsOnSameLineAsRangeStart(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
|
return positionsAreOnSameLine(range1.end, getStartPositionOfRange(range2, sourceFile), sourceFile);
|
|
}
|
|
|
|
export function positionsAreOnSameLine(pos1: number, pos2: number, sourceFile: SourceFile) {
|
|
return pos1 === pos2 ||
|
|
getLineOfLocalPosition(sourceFile, pos1) === getLineOfLocalPosition(sourceFile, pos2);
|
|
}
|
|
|
|
export function getStartPositionOfRange(range: TextRange, sourceFile: SourceFile) {
|
|
return positionIsSynthesized(range.pos) ? -1 : skipTrivia(sourceFile.text, range.pos);
|
|
}
|
|
|
|
export interface ExternalModuleInfo {
|
|
externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules
|
|
exportSpecifiers: Map<ExportSpecifier[]>; // export specifiers by name
|
|
exportedBindings: Map<Identifier[]>; // exported names of local declarations
|
|
exportedNames: Identifier[]; // all exported names local to module
|
|
exportEquals: ExportAssignment | undefined; // an export= declaration if one was present
|
|
hasExportStarsToExportValues: boolean; // whether this module contains export*
|
|
}
|
|
|
|
export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver): ExternalModuleInfo {
|
|
const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = [];
|
|
const exportSpecifiers = createMap<ExportSpecifier[]>();
|
|
const exportedBindings = createMap<Identifier[]>();
|
|
const uniqueExports = createMap<Identifier>();
|
|
let hasExportDefault = false;
|
|
let exportEquals: ExportAssignment = undefined;
|
|
let hasExportStarsToExportValues = false;
|
|
for (const node of sourceFile.statements) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ImportDeclaration:
|
|
// import "mod"
|
|
// import x from "mod"
|
|
// import * as x from "mod"
|
|
// import { x, y } from "mod"
|
|
externalImports.push(<ImportDeclaration>node);
|
|
break;
|
|
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
if ((<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference) {
|
|
// import x = require("mod")
|
|
externalImports.push(<ImportEqualsDeclaration>node);
|
|
}
|
|
|
|
break;
|
|
|
|
case SyntaxKind.ExportDeclaration:
|
|
if ((<ExportDeclaration>node).moduleSpecifier) {
|
|
if (!(<ExportDeclaration>node).exportClause) {
|
|
// export * from "mod"
|
|
externalImports.push(<ExportDeclaration>node);
|
|
hasExportStarsToExportValues = true;
|
|
}
|
|
else {
|
|
// export { x, y } from "mod"
|
|
externalImports.push(<ExportDeclaration>node);
|
|
}
|
|
}
|
|
else {
|
|
// export { x, y }
|
|
for (const specifier of (<ExportDeclaration>node).exportClause.elements) {
|
|
if (!uniqueExports[specifier.name.text]) {
|
|
const name = specifier.propertyName || specifier.name;
|
|
multiMapAdd(exportSpecifiers, name.text, specifier);
|
|
|
|
const decl = resolver.getReferencedImportDeclaration(name)
|
|
|| resolver.getReferencedValueDeclaration(name);
|
|
|
|
if (decl) {
|
|
multiMapAdd(exportedBindings, getOriginalNodeId(decl), specifier.name);
|
|
}
|
|
|
|
uniqueExports[specifier.name.text] = specifier.name;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.ExportAssignment:
|
|
if ((<ExportAssignment>node).isExportEquals && !exportEquals) {
|
|
// export = x
|
|
exportEquals = <ExportAssignment>node;
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.VariableStatement:
|
|
if (hasModifier(node, ModifierFlags.Export)) {
|
|
for (const decl of (<VariableStatement>node).declarationList.declarations) {
|
|
collectExportedVariableInfo(decl, uniqueExports);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.FunctionDeclaration:
|
|
if (hasModifier(node, ModifierFlags.Export)) {
|
|
if (hasModifier(node, ModifierFlags.Default)) {
|
|
// export default function() { }
|
|
if (!hasExportDefault) {
|
|
multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(<FunctionDeclaration>node));
|
|
hasExportDefault = true;
|
|
}
|
|
}
|
|
else {
|
|
// export function x() { }
|
|
const name = (<FunctionDeclaration>node).name;
|
|
if (!uniqueExports[name.text]) {
|
|
multiMapAdd(exportedBindings, getOriginalNodeId(node), name);
|
|
uniqueExports[name.text] = name;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.ClassDeclaration:
|
|
if (hasModifier(node, ModifierFlags.Export)) {
|
|
if (hasModifier(node, ModifierFlags.Default)) {
|
|
// export default class { }
|
|
if (!hasExportDefault) {
|
|
multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(<ClassDeclaration>node));
|
|
hasExportDefault = true;
|
|
}
|
|
}
|
|
else {
|
|
// export class x { }
|
|
const name = (<ClassDeclaration>node).name;
|
|
if (!uniqueExports[name.text]) {
|
|
multiMapAdd(exportedBindings, getOriginalNodeId(node), name);
|
|
uniqueExports[name.text] = name;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
let exportedNames: Identifier[];
|
|
for (const key in uniqueExports) {
|
|
exportedNames = ts.append(exportedNames, uniqueExports[key]);
|
|
}
|
|
|
|
return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames };
|
|
}
|
|
|
|
function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map<Identifier>) {
|
|
if (isBindingPattern(decl.name)) {
|
|
for (const element of decl.name.elements) {
|
|
if (!isOmittedExpression(element)) {
|
|
collectExportedVariableInfo(element, uniqueExports);
|
|
}
|
|
}
|
|
}
|
|
else if (!isGeneratedIdentifier(decl.name)) {
|
|
if (!uniqueExports[decl.name.text]) {
|
|
uniqueExports[decl.name.text] = decl.name;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether a name was originally the declaration name of an enum or namespace
|
|
* declaration.
|
|
*/
|
|
export function isDeclarationNameOfEnumOrNamespace(node: Identifier) {
|
|
const parseNode = getParseTreeNode(node);
|
|
if (parseNode) {
|
|
switch (parseNode.parent.kind) {
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
return parseNode === (<EnumDeclaration | ModuleDeclaration>parseNode.parent).name;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function getInitializedVariables(node: VariableDeclarationList) {
|
|
return filter(node.declarations, isInitializedVariable);
|
|
}
|
|
|
|
function isInitializedVariable(node: VariableDeclaration) {
|
|
return node.initializer !== undefined;
|
|
}
|
|
|
|
/**
|
|
* Gets a value indicating whether a node is merged with a class declaration in the same scope.
|
|
*/
|
|
export function isMergedWithClass(node: Node) {
|
|
if (node.symbol) {
|
|
for (const declaration of node.symbol.declarations) {
|
|
if (declaration.kind === SyntaxKind.ClassDeclaration && declaration !== node) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets a value indicating whether a node is the first declaration of its kind.
|
|
*
|
|
* @param node A Declaration node.
|
|
* @param kind The SyntaxKind to find among related declarations.
|
|
*/
|
|
export function isFirstDeclarationOfKind(node: Node, kind: SyntaxKind) {
|
|
return node.symbol && getDeclarationOfKind(node.symbol, kind) === node;
|
|
}
|
|
|
|
// Node tests
|
|
//
|
|
// All node tests in the following list should *not* reference parent pointers so that
|
|
// they may be used with transformations.
|
|
|
|
// Node Arrays
|
|
|
|
export function isNodeArray<T extends Node>(array: T[]): array is NodeArray<T> {
|
|
return array.hasOwnProperty("pos")
|
|
&& array.hasOwnProperty("end");
|
|
}
|
|
|
|
// Literals
|
|
|
|
export function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression {
|
|
return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral;
|
|
}
|
|
|
|
export function isLiteralKind(kind: SyntaxKind): boolean {
|
|
return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken;
|
|
}
|
|
|
|
export function isTextualLiteralKind(kind: SyntaxKind): boolean {
|
|
return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral;
|
|
}
|
|
|
|
export function isLiteralExpression(node: Node): node is LiteralExpression {
|
|
return isLiteralKind(node.kind);
|
|
}
|
|
|
|
// Pseudo-literals
|
|
|
|
export function isTemplateLiteralKind(kind: SyntaxKind): boolean {
|
|
return SyntaxKind.FirstTemplateToken <= kind && kind <= SyntaxKind.LastTemplateToken;
|
|
}
|
|
|
|
export function isTemplateHead(node: Node): node is TemplateHead {
|
|
return node.kind === SyntaxKind.TemplateHead;
|
|
}
|
|
|
|
export function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.TemplateMiddle
|
|
|| kind === SyntaxKind.TemplateTail;
|
|
}
|
|
|
|
// Identifiers
|
|
|
|
export function isIdentifier(node: Node): node is Identifier {
|
|
return node.kind === SyntaxKind.Identifier;
|
|
}
|
|
|
|
export function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier {
|
|
// Using `>` here catches both `GeneratedIdentifierKind.None` and `undefined`.
|
|
return isIdentifier(node) && node.autoGenerateKind > GeneratedIdentifierKind.None;
|
|
}
|
|
|
|
// Keywords
|
|
|
|
export function isModifier(node: Node): node is Modifier {
|
|
return isModifierKind(node.kind);
|
|
}
|
|
|
|
// Names
|
|
|
|
export function isQualifiedName(node: Node): node is QualifiedName {
|
|
return node.kind === SyntaxKind.QualifiedName;
|
|
}
|
|
|
|
export function isComputedPropertyName(node: Node): node is ComputedPropertyName {
|
|
return node.kind === SyntaxKind.ComputedPropertyName;
|
|
}
|
|
|
|
export function isEntityName(node: Node): node is EntityName {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.QualifiedName
|
|
|| kind === SyntaxKind.Identifier;
|
|
}
|
|
|
|
export function isPropertyName(node: Node): node is PropertyName {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.Identifier
|
|
|| kind === SyntaxKind.StringLiteral
|
|
|| kind === SyntaxKind.NumericLiteral
|
|
|| kind === SyntaxKind.ComputedPropertyName;
|
|
}
|
|
|
|
export function isModuleName(node: Node): node is ModuleName {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.Identifier
|
|
|| kind === SyntaxKind.StringLiteral;
|
|
}
|
|
|
|
export function isBindingName(node: Node): node is BindingName {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.Identifier
|
|
|| kind === SyntaxKind.ObjectBindingPattern
|
|
|| kind === SyntaxKind.ArrayBindingPattern;
|
|
}
|
|
|
|
// Signature elements
|
|
|
|
export function isTypeParameter(node: Node): node is TypeParameterDeclaration {
|
|
return node.kind === SyntaxKind.TypeParameter;
|
|
}
|
|
|
|
export function isParameter(node: Node): node is ParameterDeclaration {
|
|
return node.kind === SyntaxKind.Parameter;
|
|
}
|
|
|
|
export function isDecorator(node: Node): node is Decorator {
|
|
return node.kind === SyntaxKind.Decorator;
|
|
}
|
|
|
|
// Type members
|
|
|
|
export function isMethodDeclaration(node: Node): node is MethodDeclaration {
|
|
return node.kind === SyntaxKind.MethodDeclaration;
|
|
}
|
|
|
|
export function isClassElement(node: Node): node is ClassElement {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.Constructor
|
|
|| kind === SyntaxKind.PropertyDeclaration
|
|
|| kind === SyntaxKind.MethodDeclaration
|
|
|| kind === SyntaxKind.GetAccessor
|
|
|| kind === SyntaxKind.SetAccessor
|
|
|| kind === SyntaxKind.IndexSignature
|
|
|| kind === SyntaxKind.SemicolonClassElement;
|
|
}
|
|
|
|
export function isObjectLiteralElementLike(node: Node): node is ObjectLiteralElementLike {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.PropertyAssignment
|
|
|| kind === SyntaxKind.ShorthandPropertyAssignment
|
|
|| kind === SyntaxKind.SpreadAssignment
|
|
|| kind === SyntaxKind.MethodDeclaration
|
|
|| kind === SyntaxKind.GetAccessor
|
|
|| kind === SyntaxKind.SetAccessor
|
|
|| kind === SyntaxKind.MissingDeclaration;
|
|
}
|
|
|
|
// Type
|
|
|
|
function isTypeNodeKind(kind: SyntaxKind) {
|
|
return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode)
|
|
|| kind === SyntaxKind.AnyKeyword
|
|
|| kind === SyntaxKind.NumberKeyword
|
|
|| kind === SyntaxKind.BooleanKeyword
|
|
|| kind === SyntaxKind.StringKeyword
|
|
|| kind === SyntaxKind.SymbolKeyword
|
|
|| kind === SyntaxKind.VoidKeyword
|
|
|| kind === SyntaxKind.NeverKeyword
|
|
|| kind === SyntaxKind.ExpressionWithTypeArguments;
|
|
}
|
|
|
|
/**
|
|
* Node test that determines whether a node is a valid type node.
|
|
* This differs from the `isPartOfTypeNode` function which determines whether a node is *part*
|
|
* of a TypeNode.
|
|
*/
|
|
export function isTypeNode(node: Node): node is TypeNode {
|
|
return isTypeNodeKind(node.kind);
|
|
}
|
|
|
|
// Binding patterns
|
|
|
|
export function isBindingPattern(node: Node): node is BindingPattern {
|
|
if (node) {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.ArrayBindingPattern
|
|
|| kind === SyntaxKind.ObjectBindingPattern;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function isBindingElement(node: Node): node is BindingElement {
|
|
return node.kind === SyntaxKind.BindingElement;
|
|
}
|
|
|
|
export function isArrayBindingElement(node: Node): node is ArrayBindingElement {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.BindingElement
|
|
|| kind === SyntaxKind.OmittedExpression;
|
|
}
|
|
|
|
// Expression
|
|
|
|
export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression {
|
|
return node.kind === SyntaxKind.ArrayLiteralExpression;
|
|
}
|
|
|
|
export function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression {
|
|
return node.kind === SyntaxKind.ObjectLiteralExpression;
|
|
}
|
|
|
|
export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression {
|
|
return node.kind === SyntaxKind.PropertyAccessExpression;
|
|
}
|
|
|
|
export function isElementAccessExpression(node: Node): node is ElementAccessExpression {
|
|
return node.kind === SyntaxKind.ElementAccessExpression;
|
|
}
|
|
|
|
export function isBinaryExpression(node: Node): node is BinaryExpression {
|
|
return node.kind === SyntaxKind.BinaryExpression;
|
|
}
|
|
|
|
export function isConditionalExpression(node: Node): node is ConditionalExpression {
|
|
return node.kind === SyntaxKind.ConditionalExpression;
|
|
}
|
|
|
|
export function isCallExpression(node: Node): node is CallExpression {
|
|
return node.kind === SyntaxKind.CallExpression;
|
|
}
|
|
|
|
export function isTemplateLiteral(node: Node): node is TemplateLiteral {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.TemplateExpression
|
|
|| kind === SyntaxKind.NoSubstitutionTemplateLiteral;
|
|
}
|
|
|
|
export function isSpreadExpression(node: Node): node is SpreadElement {
|
|
return node.kind === SyntaxKind.SpreadElement;
|
|
}
|
|
|
|
export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments {
|
|
return node.kind === SyntaxKind.ExpressionWithTypeArguments;
|
|
}
|
|
|
|
function isLeftHandSideExpressionKind(kind: SyntaxKind): boolean {
|
|
return kind === SyntaxKind.PropertyAccessExpression
|
|
|| kind === SyntaxKind.ElementAccessExpression
|
|
|| kind === SyntaxKind.NewExpression
|
|
|| kind === SyntaxKind.CallExpression
|
|
|| kind === SyntaxKind.JsxElement
|
|
|| kind === SyntaxKind.JsxSelfClosingElement
|
|
|| kind === SyntaxKind.TaggedTemplateExpression
|
|
|| kind === SyntaxKind.ArrayLiteralExpression
|
|
|| kind === SyntaxKind.ParenthesizedExpression
|
|
|| kind === SyntaxKind.ObjectLiteralExpression
|
|
|| kind === SyntaxKind.ClassExpression
|
|
|| kind === SyntaxKind.FunctionExpression
|
|
|| kind === SyntaxKind.Identifier
|
|
|| kind === SyntaxKind.RegularExpressionLiteral
|
|
|| kind === SyntaxKind.NumericLiteral
|
|
|| kind === SyntaxKind.StringLiteral
|
|
|| kind === SyntaxKind.NoSubstitutionTemplateLiteral
|
|
|| kind === SyntaxKind.TemplateExpression
|
|
|| kind === SyntaxKind.FalseKeyword
|
|
|| kind === SyntaxKind.NullKeyword
|
|
|| kind === SyntaxKind.ThisKeyword
|
|
|| kind === SyntaxKind.TrueKeyword
|
|
|| kind === SyntaxKind.SuperKeyword
|
|
|| kind === SyntaxKind.NonNullExpression;
|
|
}
|
|
|
|
export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression {
|
|
return isLeftHandSideExpressionKind(skipPartiallyEmittedExpressions(node).kind);
|
|
}
|
|
|
|
function isUnaryExpressionKind(kind: SyntaxKind): boolean {
|
|
return kind === SyntaxKind.PrefixUnaryExpression
|
|
|| kind === SyntaxKind.PostfixUnaryExpression
|
|
|| kind === SyntaxKind.DeleteExpression
|
|
|| kind === SyntaxKind.TypeOfExpression
|
|
|| kind === SyntaxKind.VoidExpression
|
|
|| kind === SyntaxKind.AwaitExpression
|
|
|| kind === SyntaxKind.TypeAssertionExpression
|
|
|| isLeftHandSideExpressionKind(kind);
|
|
}
|
|
|
|
export function isUnaryExpression(node: Node): node is UnaryExpression {
|
|
return isUnaryExpressionKind(skipPartiallyEmittedExpressions(node).kind);
|
|
}
|
|
|
|
function isExpressionKind(kind: SyntaxKind) {
|
|
return kind === SyntaxKind.ConditionalExpression
|
|
|| kind === SyntaxKind.YieldExpression
|
|
|| kind === SyntaxKind.ArrowFunction
|
|
|| kind === SyntaxKind.BinaryExpression
|
|
|| kind === SyntaxKind.SpreadElement
|
|
|| kind === SyntaxKind.AsExpression
|
|
|| kind === SyntaxKind.OmittedExpression
|
|
|| isUnaryExpressionKind(kind);
|
|
}
|
|
|
|
export function isExpression(node: Node): node is Expression {
|
|
return isExpressionKind(skipPartiallyEmittedExpressions(node).kind);
|
|
}
|
|
|
|
export function isAssertionExpression(node: Node): node is AssertionExpression {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.TypeAssertionExpression
|
|
|| kind === SyntaxKind.AsExpression;
|
|
}
|
|
|
|
export function isPartiallyEmittedExpression(node: Node): node is PartiallyEmittedExpression {
|
|
return node.kind === SyntaxKind.PartiallyEmittedExpression;
|
|
}
|
|
|
|
export function isNotEmittedStatement(node: Node): node is NotEmittedStatement {
|
|
return node.kind === SyntaxKind.NotEmittedStatement;
|
|
}
|
|
|
|
export function isNotEmittedOrPartiallyEmittedNode(node: Node): node is NotEmittedStatement | PartiallyEmittedExpression {
|
|
return isNotEmittedStatement(node)
|
|
|| isPartiallyEmittedExpression(node);
|
|
}
|
|
|
|
export function isOmittedExpression(node: Node): node is OmittedExpression {
|
|
return node.kind === SyntaxKind.OmittedExpression;
|
|
}
|
|
|
|
// Misc
|
|
|
|
export function isTemplateSpan(node: Node): node is TemplateSpan {
|
|
return node.kind === SyntaxKind.TemplateSpan;
|
|
}
|
|
|
|
// Element
|
|
|
|
export function isBlock(node: Node): node is Block {
|
|
return node.kind === SyntaxKind.Block;
|
|
}
|
|
|
|
export function isConciseBody(node: Node): node is ConciseBody {
|
|
return isBlock(node)
|
|
|| isExpression(node);
|
|
}
|
|
|
|
export function isFunctionBody(node: Node): node is FunctionBody {
|
|
return isBlock(node);
|
|
}
|
|
|
|
export function isForInitializer(node: Node): node is ForInitializer {
|
|
return isVariableDeclarationList(node)
|
|
|| isExpression(node);
|
|
}
|
|
|
|
export function isVariableDeclaration(node: Node): node is VariableDeclaration {
|
|
return node.kind === SyntaxKind.VariableDeclaration;
|
|
}
|
|
|
|
export function isVariableDeclarationList(node: Node): node is VariableDeclarationList {
|
|
return node.kind === SyntaxKind.VariableDeclarationList;
|
|
}
|
|
|
|
export function isCaseBlock(node: Node): node is CaseBlock {
|
|
return node.kind === SyntaxKind.CaseBlock;
|
|
}
|
|
|
|
export function isModuleBody(node: Node): node is ModuleBody {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.ModuleBlock
|
|
|| kind === SyntaxKind.ModuleDeclaration;
|
|
}
|
|
|
|
export function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration {
|
|
return node.kind === SyntaxKind.ImportEqualsDeclaration;
|
|
}
|
|
|
|
export function isImportClause(node: Node): node is ImportClause {
|
|
return node.kind === SyntaxKind.ImportClause;
|
|
}
|
|
|
|
export function isNamedImportBindings(node: Node): node is NamedImportBindings {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.NamedImports
|
|
|| kind === SyntaxKind.NamespaceImport;
|
|
}
|
|
|
|
export function isImportSpecifier(node: Node): node is ImportSpecifier {
|
|
return node.kind === SyntaxKind.ImportSpecifier;
|
|
}
|
|
|
|
export function isNamedExports(node: Node): node is NamedExports {
|
|
return node.kind === SyntaxKind.NamedExports;
|
|
}
|
|
|
|
export function isExportSpecifier(node: Node): node is ExportSpecifier {
|
|
return node.kind === SyntaxKind.ExportSpecifier;
|
|
}
|
|
|
|
export function isModuleOrEnumDeclaration(node: Node): node is ModuleDeclaration | EnumDeclaration {
|
|
return node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.EnumDeclaration;
|
|
}
|
|
|
|
function isDeclarationKind(kind: SyntaxKind) {
|
|
return kind === SyntaxKind.ArrowFunction
|
|
|| kind === SyntaxKind.BindingElement
|
|
|| kind === SyntaxKind.ClassDeclaration
|
|
|| kind === SyntaxKind.ClassExpression
|
|
|| kind === SyntaxKind.Constructor
|
|
|| kind === SyntaxKind.EnumDeclaration
|
|
|| kind === SyntaxKind.EnumMember
|
|
|| kind === SyntaxKind.ExportSpecifier
|
|
|| kind === SyntaxKind.FunctionDeclaration
|
|
|| kind === SyntaxKind.FunctionExpression
|
|
|| kind === SyntaxKind.GetAccessor
|
|
|| kind === SyntaxKind.ImportClause
|
|
|| kind === SyntaxKind.ImportEqualsDeclaration
|
|
|| kind === SyntaxKind.ImportSpecifier
|
|
|| kind === SyntaxKind.InterfaceDeclaration
|
|
|| kind === SyntaxKind.MethodDeclaration
|
|
|| kind === SyntaxKind.MethodSignature
|
|
|| kind === SyntaxKind.ModuleDeclaration
|
|
|| kind === SyntaxKind.NamespaceExportDeclaration
|
|
|| kind === SyntaxKind.NamespaceImport
|
|
|| kind === SyntaxKind.Parameter
|
|
|| kind === SyntaxKind.PropertyAssignment
|
|
|| kind === SyntaxKind.PropertyDeclaration
|
|
|| kind === SyntaxKind.PropertySignature
|
|
|| kind === SyntaxKind.SetAccessor
|
|
|| kind === SyntaxKind.ShorthandPropertyAssignment
|
|
|| kind === SyntaxKind.TypeAliasDeclaration
|
|
|| kind === SyntaxKind.TypeParameter
|
|
|| kind === SyntaxKind.VariableDeclaration
|
|
|| kind === SyntaxKind.JSDocTypedefTag;
|
|
}
|
|
|
|
function isDeclarationStatementKind(kind: SyntaxKind) {
|
|
return kind === SyntaxKind.FunctionDeclaration
|
|
|| kind === SyntaxKind.MissingDeclaration
|
|
|| kind === SyntaxKind.ClassDeclaration
|
|
|| kind === SyntaxKind.InterfaceDeclaration
|
|
|| kind === SyntaxKind.TypeAliasDeclaration
|
|
|| kind === SyntaxKind.EnumDeclaration
|
|
|| kind === SyntaxKind.ModuleDeclaration
|
|
|| kind === SyntaxKind.ImportDeclaration
|
|
|| kind === SyntaxKind.ImportEqualsDeclaration
|
|
|| kind === SyntaxKind.ExportDeclaration
|
|
|| kind === SyntaxKind.ExportAssignment
|
|
|| kind === SyntaxKind.NamespaceExportDeclaration;
|
|
}
|
|
|
|
function isStatementKindButNotDeclarationKind(kind: SyntaxKind) {
|
|
return kind === SyntaxKind.BreakStatement
|
|
|| kind === SyntaxKind.ContinueStatement
|
|
|| kind === SyntaxKind.DebuggerStatement
|
|
|| kind === SyntaxKind.DoStatement
|
|
|| kind === SyntaxKind.ExpressionStatement
|
|
|| kind === SyntaxKind.EmptyStatement
|
|
|| kind === SyntaxKind.ForInStatement
|
|
|| kind === SyntaxKind.ForOfStatement
|
|
|| kind === SyntaxKind.ForStatement
|
|
|| kind === SyntaxKind.IfStatement
|
|
|| kind === SyntaxKind.LabeledStatement
|
|
|| kind === SyntaxKind.ReturnStatement
|
|
|| kind === SyntaxKind.SwitchStatement
|
|
|| kind === SyntaxKind.ThrowStatement
|
|
|| kind === SyntaxKind.TryStatement
|
|
|| kind === SyntaxKind.VariableStatement
|
|
|| kind === SyntaxKind.WhileStatement
|
|
|| kind === SyntaxKind.WithStatement
|
|
|| kind === SyntaxKind.NotEmittedStatement
|
|
|| kind === SyntaxKind.EndOfDeclarationMarker
|
|
|| kind === SyntaxKind.MergeDeclarationMarker;
|
|
}
|
|
|
|
export function isDeclaration(node: Node): node is Declaration {
|
|
return isDeclarationKind(node.kind);
|
|
}
|
|
|
|
export function isDeclarationStatement(node: Node): node is DeclarationStatement {
|
|
return isDeclarationStatementKind(node.kind);
|
|
}
|
|
|
|
/**
|
|
* Determines whether the node is a statement that is not also a declaration
|
|
*/
|
|
export function isStatementButNotDeclaration(node: Node): node is Statement {
|
|
return isStatementKindButNotDeclarationKind(node.kind);
|
|
}
|
|
|
|
export function isStatement(node: Node): node is Statement {
|
|
const kind = node.kind;
|
|
return isStatementKindButNotDeclarationKind(kind)
|
|
|| isDeclarationStatementKind(kind)
|
|
|| kind === SyntaxKind.Block;
|
|
}
|
|
|
|
// Module references
|
|
|
|
export function isModuleReference(node: Node): node is ModuleReference {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.ExternalModuleReference
|
|
|| kind === SyntaxKind.QualifiedName
|
|
|| kind === SyntaxKind.Identifier;
|
|
}
|
|
|
|
// JSX
|
|
|
|
export function isJsxOpeningElement(node: Node): node is JsxOpeningElement {
|
|
return node.kind === SyntaxKind.JsxOpeningElement;
|
|
}
|
|
|
|
export function isJsxClosingElement(node: Node): node is JsxClosingElement {
|
|
return node.kind === SyntaxKind.JsxClosingElement;
|
|
}
|
|
|
|
export function isJsxTagNameExpression(node: Node): node is JsxTagNameExpression {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.ThisKeyword
|
|
|| kind === SyntaxKind.Identifier
|
|
|| kind === SyntaxKind.PropertyAccessExpression;
|
|
}
|
|
|
|
export function isJsxChild(node: Node): node is JsxChild {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.JsxElement
|
|
|| kind === SyntaxKind.JsxExpression
|
|
|| kind === SyntaxKind.JsxSelfClosingElement
|
|
|| kind === SyntaxKind.JsxText;
|
|
}
|
|
|
|
export function isJsxAttributeLike(node: Node): node is JsxAttributeLike {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.JsxAttribute
|
|
|| kind === SyntaxKind.JsxSpreadAttribute;
|
|
}
|
|
|
|
export function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute {
|
|
return node.kind === SyntaxKind.JsxSpreadAttribute;
|
|
}
|
|
|
|
export function isJsxAttribute(node: Node): node is JsxAttribute {
|
|
return node.kind === SyntaxKind.JsxAttribute;
|
|
}
|
|
|
|
export function isStringLiteralOrJsxExpression(node: Node): node is StringLiteral | JsxExpression {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.StringLiteral
|
|
|| kind === SyntaxKind.JsxExpression;
|
|
}
|
|
|
|
// Clauses
|
|
|
|
export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.CaseClause
|
|
|| kind === SyntaxKind.DefaultClause;
|
|
}
|
|
|
|
export function isHeritageClause(node: Node): node is HeritageClause {
|
|
return node.kind === SyntaxKind.HeritageClause;
|
|
}
|
|
|
|
export function isCatchClause(node: Node): node is CatchClause {
|
|
return node.kind === SyntaxKind.CatchClause;
|
|
}
|
|
|
|
|
|
// Property assignments
|
|
|
|
export function isPropertyAssignment(node: Node): node is PropertyAssignment {
|
|
return node.kind === SyntaxKind.PropertyAssignment;
|
|
}
|
|
|
|
export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment {
|
|
return node.kind === SyntaxKind.ShorthandPropertyAssignment;
|
|
}
|
|
|
|
// Enum
|
|
|
|
export function isEnumMember(node: Node): node is EnumMember {
|
|
return node.kind === SyntaxKind.EnumMember;
|
|
}
|
|
|
|
// Top-level nodes
|
|
export function isSourceFile(node: Node): node is SourceFile {
|
|
return node.kind === SyntaxKind.SourceFile;
|
|
}
|
|
|
|
export function isWatchSet(options: CompilerOptions) {
|
|
// Firefox has Object.prototype.watch
|
|
return options.watch && options.hasOwnProperty("watch");
|
|
}
|
|
}
|
|
|
|
namespace ts {
|
|
export function getDefaultLibFileName(options: CompilerOptions): string {
|
|
switch (options.target) {
|
|
case ScriptTarget.ESNext:
|
|
case ScriptTarget.ES2017:
|
|
return "lib.es2017.d.ts";
|
|
case ScriptTarget.ES2016:
|
|
return "lib.es2016.d.ts";
|
|
case ScriptTarget.ES2015:
|
|
return "lib.es6.d.ts";
|
|
|
|
default:
|
|
return "lib.d.ts";
|
|
}
|
|
}
|
|
|
|
export function textSpanEnd(span: TextSpan) {
|
|
return span.start + span.length;
|
|
}
|
|
|
|
export function textSpanIsEmpty(span: TextSpan) {
|
|
return span.length === 0;
|
|
}
|
|
|
|
export function textSpanContainsPosition(span: TextSpan, position: number) {
|
|
return position >= span.start && position < textSpanEnd(span);
|
|
}
|
|
|
|
// Returns true if 'span' contains 'other'.
|
|
export function textSpanContainsTextSpan(span: TextSpan, other: TextSpan) {
|
|
return other.start >= span.start && textSpanEnd(other) <= textSpanEnd(span);
|
|
}
|
|
|
|
export function textSpanOverlapsWith(span: TextSpan, other: TextSpan) {
|
|
const overlapStart = Math.max(span.start, other.start);
|
|
const overlapEnd = Math.min(textSpanEnd(span), textSpanEnd(other));
|
|
return overlapStart < overlapEnd;
|
|
}
|
|
|
|
export function textSpanOverlap(span1: TextSpan, span2: TextSpan) {
|
|
const overlapStart = Math.max(span1.start, span2.start);
|
|
const overlapEnd = Math.min(textSpanEnd(span1), textSpanEnd(span2));
|
|
if (overlapStart < overlapEnd) {
|
|
return createTextSpanFromBounds(overlapStart, overlapEnd);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function textSpanIntersectsWithTextSpan(span: TextSpan, other: TextSpan) {
|
|
return other.start <= textSpanEnd(span) && textSpanEnd(other) >= span.start;
|
|
}
|
|
|
|
export function textSpanIntersectsWith(span: TextSpan, start: number, length: number) {
|
|
const end = start + length;
|
|
return start <= textSpanEnd(span) && end >= span.start;
|
|
}
|
|
|
|
export function decodedTextSpanIntersectsWith(start1: number, length1: number, start2: number, length2: number) {
|
|
const end1 = start1 + length1;
|
|
const end2 = start2 + length2;
|
|
return start2 <= end1 && end2 >= start1;
|
|
}
|
|
|
|
export function textSpanIntersectsWithPosition(span: TextSpan, position: number) {
|
|
return position <= textSpanEnd(span) && position >= span.start;
|
|
}
|
|
|
|
export function textSpanIntersection(span1: TextSpan, span2: TextSpan) {
|
|
const intersectStart = Math.max(span1.start, span2.start);
|
|
const intersectEnd = Math.min(textSpanEnd(span1), textSpanEnd(span2));
|
|
if (intersectStart <= intersectEnd) {
|
|
return createTextSpanFromBounds(intersectStart, intersectEnd);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function createTextSpan(start: number, length: number): TextSpan {
|
|
if (start < 0) {
|
|
throw new Error("start < 0");
|
|
}
|
|
if (length < 0) {
|
|
throw new Error("length < 0");
|
|
}
|
|
|
|
return { start, length };
|
|
}
|
|
|
|
export function createTextSpanFromBounds(start: number, end: number) {
|
|
return createTextSpan(start, end - start);
|
|
}
|
|
|
|
export function textChangeRangeNewSpan(range: TextChangeRange) {
|
|
return createTextSpan(range.span.start, range.newLength);
|
|
}
|
|
|
|
export function textChangeRangeIsUnchanged(range: TextChangeRange) {
|
|
return textSpanIsEmpty(range.span) && range.newLength === 0;
|
|
}
|
|
|
|
export function createTextChangeRange(span: TextSpan, newLength: number): TextChangeRange {
|
|
if (newLength < 0) {
|
|
throw new Error("newLength < 0");
|
|
}
|
|
|
|
return { span, newLength };
|
|
}
|
|
|
|
export let unchangedTextChangeRange = createTextChangeRange(createTextSpan(0, 0), 0);
|
|
|
|
/**
|
|
* Called to merge all the changes that occurred across several versions of a script snapshot
|
|
* into a single change. i.e. if a user keeps making successive edits to a script we will
|
|
* have a text change from V1 to V2, V2 to V3, ..., Vn.
|
|
*
|
|
* This function will then merge those changes into a single change range valid between V1 and
|
|
* Vn.
|
|
*/
|
|
export function collapseTextChangeRangesAcrossMultipleVersions(changes: TextChangeRange[]): TextChangeRange {
|
|
if (changes.length === 0) {
|
|
return unchangedTextChangeRange;
|
|
}
|
|
|
|
if (changes.length === 1) {
|
|
return changes[0];
|
|
}
|
|
|
|
// We change from talking about { { oldStart, oldLength }, newLength } to { oldStart, oldEnd, newEnd }
|
|
// as it makes things much easier to reason about.
|
|
const change0 = changes[0];
|
|
|
|
let oldStartN = change0.span.start;
|
|
let oldEndN = textSpanEnd(change0.span);
|
|
let newEndN = oldStartN + change0.newLength;
|
|
|
|
for (let i = 1; i < changes.length; i++) {
|
|
const nextChange = changes[i];
|
|
|
|
// Consider the following case:
|
|
// i.e. two edits. The first represents the text change range { { 10, 50 }, 30 }. i.e. The span starting
|
|
// at 10, with length 50 is reduced to length 30. The second represents the text change range { { 30, 30 }, 40 }.
|
|
// i.e. the span starting at 30 with length 30 is increased to length 40.
|
|
//
|
|
// 0 10 20 30 40 50 60 70 80 90 100
|
|
// -------------------------------------------------------------------------------------------------------
|
|
// | /
|
|
// | /----
|
|
// T1 | /----
|
|
// | /----
|
|
// | /----
|
|
// -------------------------------------------------------------------------------------------------------
|
|
// | \
|
|
// | \
|
|
// T2 | \
|
|
// | \
|
|
// | \
|
|
// -------------------------------------------------------------------------------------------------------
|
|
//
|
|
// Merging these turns out to not be too difficult. First, determining the new start of the change is trivial
|
|
// it's just the min of the old and new starts. i.e.:
|
|
//
|
|
// 0 10 20 30 40 50 60 70 80 90 100
|
|
// ------------------------------------------------------------*------------------------------------------
|
|
// | /
|
|
// | /----
|
|
// T1 | /----
|
|
// | /----
|
|
// | /----
|
|
// ----------------------------------------$-------------------$------------------------------------------
|
|
// . | \
|
|
// . | \
|
|
// T2 . | \
|
|
// . | \
|
|
// . | \
|
|
// ----------------------------------------------------------------------*--------------------------------
|
|
//
|
|
// (Note the dots represent the newly inferred start.
|
|
// Determining the new and old end is also pretty simple. Basically it boils down to paying attention to the
|
|
// absolute positions at the asterisks, and the relative change between the dollar signs. Basically, we see
|
|
// which if the two $'s precedes the other, and we move that one forward until they line up. in this case that
|
|
// means:
|
|
//
|
|
// 0 10 20 30 40 50 60 70 80 90 100
|
|
// --------------------------------------------------------------------------------*----------------------
|
|
// | /
|
|
// | /----
|
|
// T1 | /----
|
|
// | /----
|
|
// | /----
|
|
// ------------------------------------------------------------$------------------------------------------
|
|
// . | \
|
|
// . | \
|
|
// T2 . | \
|
|
// . | \
|
|
// . | \
|
|
// ----------------------------------------------------------------------*--------------------------------
|
|
//
|
|
// In other words (in this case), we're recognizing that the second edit happened after where the first edit
|
|
// ended with a delta of 20 characters (60 - 40). Thus, if we go back in time to where the first edit started
|
|
// that's the same as if we started at char 80 instead of 60.
|
|
//
|
|
// As it so happens, the same logic applies if the second edit precedes the first edit. In that case rather
|
|
// than pushing the first edit forward to match the second, we'll push the second edit forward to match the
|
|
// first.
|
|
//
|
|
// In this case that means we have { oldStart: 10, oldEnd: 80, newEnd: 70 } or, in TextChangeRange
|
|
// semantics: { { start: 10, length: 70 }, newLength: 60 }
|
|
//
|
|
// The math then works out as follows.
|
|
// If we have { oldStart1, oldEnd1, newEnd1 } and { oldStart2, oldEnd2, newEnd2 } then we can compute the
|
|
// final result like so:
|
|
//
|
|
// {
|
|
// oldStart3: Min(oldStart1, oldStart2),
|
|
// oldEnd3 : Max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)),
|
|
// newEnd3 : Max(newEnd2, newEnd2 + (newEnd1 - oldEnd2))
|
|
// }
|
|
|
|
const oldStart1 = oldStartN;
|
|
const oldEnd1 = oldEndN;
|
|
const newEnd1 = newEndN;
|
|
|
|
const oldStart2 = nextChange.span.start;
|
|
const oldEnd2 = textSpanEnd(nextChange.span);
|
|
const newEnd2 = oldStart2 + nextChange.newLength;
|
|
|
|
oldStartN = Math.min(oldStart1, oldStart2);
|
|
oldEndN = Math.max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1));
|
|
newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2));
|
|
}
|
|
|
|
return createTextChangeRange(createTextSpanFromBounds(oldStartN, oldEndN), /*newLength:*/ newEndN - oldStartN);
|
|
}
|
|
|
|
export function getTypeParameterOwner(d: Declaration): Declaration {
|
|
if (d && d.kind === SyntaxKind.TypeParameter) {
|
|
for (let current: Node = d; current; current = current.parent) {
|
|
if (isFunctionLike(current) || isClassLike(current) || current.kind === SyntaxKind.InterfaceDeclaration) {
|
|
return <Declaration>current;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function isParameterPropertyDeclaration(node: ParameterDeclaration): boolean {
|
|
return hasModifier(node, ModifierFlags.ParameterPropertyModifier) && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent);
|
|
}
|
|
|
|
function walkUpBindingElementsAndPatterns(node: Node): Node {
|
|
while (node && (node.kind === SyntaxKind.BindingElement || isBindingPattern(node))) {
|
|
node = node.parent;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
export function getCombinedModifierFlags(node: Node): ModifierFlags {
|
|
node = walkUpBindingElementsAndPatterns(node);
|
|
let flags = getModifierFlags(node);
|
|
if (node.kind === SyntaxKind.VariableDeclaration) {
|
|
node = node.parent;
|
|
}
|
|
|
|
if (node && node.kind === SyntaxKind.VariableDeclarationList) {
|
|
flags |= getModifierFlags(node);
|
|
node = node.parent;
|
|
}
|
|
|
|
if (node && node.kind === SyntaxKind.VariableStatement) {
|
|
flags |= getModifierFlags(node);
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
// Returns the node flags for this node and all relevant parent nodes. This is done so that
|
|
// nodes like variable declarations and binding elements can returned a view of their flags
|
|
// that includes the modifiers from their container. i.e. flags like export/declare aren't
|
|
// stored on the variable declaration directly, but on the containing variable statement
|
|
// (if it has one). Similarly, flags for let/const are store on the variable declaration
|
|
// list. By calling this function, all those flags are combined so that the client can treat
|
|
// the node as if it actually had those flags.
|
|
export function getCombinedNodeFlags(node: Node): NodeFlags {
|
|
node = walkUpBindingElementsAndPatterns(node);
|
|
|
|
let flags = node.flags;
|
|
if (node.kind === SyntaxKind.VariableDeclaration) {
|
|
node = node.parent;
|
|
}
|
|
|
|
if (node && node.kind === SyntaxKind.VariableDeclarationList) {
|
|
flags |= node.flags;
|
|
node = node.parent;
|
|
}
|
|
|
|
if (node && node.kind === SyntaxKind.VariableStatement) {
|
|
flags |= node.flags;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
}
|