TypeScript/src/compiler/utilities.ts

1819 lines
78 KiB
TypeScript

/// <reference path="binder.ts" />
module ts {
export interface ReferencePathMatchResult {
fileReference?: FileReference
diagnosticMessage?: DiagnosticMessage
isNoDefaultLib?: boolean
}
export interface SynthesizedNode extends Node {
leadingCommentRanges?: CommentRange[];
trailingCommentRanges?: CommentRange[];
startsOnNewLine: boolean;
}
export function getDeclarationOfKind(symbol: Symbol, kind: SyntaxKind): Declaration {
let declarations = symbol.declarations;
for (let 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[];
getCommonSourceDirectory(): string;
getCanonicalFileName(fileName: string): string;
getNewLine(): string;
writeFile: WriteFileCallback;
}
// Pool writers to avoid needing to allocate them for every symbol we write.
let stringWriters: StringSymbolWriter[] = [];
export function getSingleLineStringWriter(): StringSymbolWriter {
if (stringWriters.length == 0) {
let str = "";
let 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: () => { },
decreaseIndent: () => { },
clear: () => str = "",
trackSymbol: () => { }
};
}
return stringWriters.pop();
}
export function releaseStringWriter(writer: StringSymbolWriter) {
writer.clear()
stringWriters.push(writer);
}
export function getFullWidth(node: Node) {
return node.end - node.pos;
}
// Returns true if this node contains a parse error anywhere underneath it.
export function containsParseError(node: Node): boolean {
aggregateChildData(node);
return (node.parserContextFlags & ParserContextFlags.ThisNodeOrAnySubNodesHasError) !== 0
}
function aggregateChildData(node: Node): void {
if (!(node.parserContextFlags & ParserContextFlags.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.
let thisNodeOrAnySubNodesHasError = ((node.parserContextFlags & ParserContextFlags.ThisNodeHasError) !== 0) ||
forEachChild(node, containsParseError);
// If so, mark ourselves accordingly.
if (thisNodeOrAnySubNodesHasError) {
node.parserContextFlags |= ParserContextFlags.ThisNodeOrAnySubNodesHasError;
}
// Also mark that we've propogated 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.parserContextFlags |= ParserContextFlags.HasAggregatedChildData;
}
}
export function getSourceFileOfNode(node: Node): SourceFile {
while (node && node.kind !== SyntaxKind.SourceFile) {
node = node.parent;
}
return <SourceFile>node;
}
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 {
let file = getSourceFileOfNode(node);
let loc = getLineAndCharacterOfPosition(file, node.pos);
return `${ file.fileName }(${ loc.line + 1 },${ loc.character + 1 })`;
}
export function getStartPosOfNode(node: Node): number {
return node.pos;
}
// Returns true if this node is missing from the actual source code. 'missing' is different
// from 'undefined/defined'. When a node is undefined (which can happen for optional nodes
// in the tree), it is definitel 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) {
return true;
}
return node.pos === node.end && node.kind !== SyntaxKind.EndOfFileToken;
}
export function nodeIsPresent(node: Node) {
return !nodeIsMissing(node);
}
export function getTokenPosOfNode(node: Node, sourceFile?: SourceFile): 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;
}
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos);
}
export function getSourceTextOfNodeFromSourceFile(sourceFile: SourceFile, node: Node): string {
if (nodeIsMissing(node)) {
return "";
}
let text = sourceFile.text;
return text.substring(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): string {
return getSourceTextOfNodeFromSourceFile(getSourceFileOfNode(node), node);
}
// 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(/\W/g, "_");
}
export function isBlockOrCatchScoped(declaration: Declaration) {
return (getCombinedNodeFlags(declaration) & NodeFlags.BlockScoped) !== 0 ||
isCatchClauseVariableDeclaration(declaration);
}
export function getEnclosingBlockScopeContainer(node: Node): Node {
let current = node;
while (current) {
if (isFunctionLike(current)) {
return current;
}
switch (current.kind) {
case SyntaxKind.SourceFile:
case SyntaxKind.CaseBlock:
case SyntaxKind.CatchClause:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
return current;
case SyntaxKind.Block:
// function block is not considered block-scope container
// see comment in binder.ts: bind(...), case for SyntaxKind.Block
if (!isFunctionLike(current.parent)) {
return current;
}
}
current = current.parent;
}
}
export function isCatchClauseVariableDeclaration(declaration: Declaration) {
return declaration &&
declaration.kind === SyntaxKind.VariableDeclaration &&
declaration.parent &&
declaration.parent.kind === SyntaxKind.CatchClause;
}
// 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 createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): Diagnostic {
let sourceFile = getSourceFileOfNode(node);
let span = getErrorSpanForNode(sourceFile, node);
return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2);
}
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): Diagnostic {
let sourceFile = getSourceFileOfNode(node);
let 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
};
}
/* @internal */
export function getSpanOfTokenAtPosition(sourceFile: SourceFile, pos: number): TextSpan {
let scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ true, sourceFile.text);
scanner.setTextPos(pos);
scanner.scan();
let start = scanner.getTokenPos();
return createTextSpanFromBounds(start, scanner.getTextPos());
}
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:
errorNode = (<Declaration>node).name;
break;
}
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);
}
let pos = nodeIsMissing(errorNode)
? errorNode.pos
: skipTrivia(sourceFile.text, errorNode.pos);
return createTextSpanFromBounds(pos, errorNode.end);
}
export function isExternalModule(file: SourceFile): boolean {
return file.externalModuleIndicator !== undefined;
}
export function isDeclarationFile(file: SourceFile): boolean {
return (file.flags & NodeFlags.DeclarationFile) !== 0;
}
export function isConstEnumDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.EnumDeclaration && isConst(node);
}
function walkUpBindingElementsAndPatterns(node: Node): Node {
while (node && (node.kind === SyntaxKind.BindingElement || isBindingPattern(node))) {
node = node.parent;
}
return node;
}
// 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;
}
export function isConst(node: Node): boolean {
return !!(getCombinedNodeFlags(node) & NodeFlags.Const);
}
export function isLet(node: Node): boolean {
return !!(getCombinedNodeFlags(node) & NodeFlags.Let);
}
export function isPrologueDirective(node: Node): boolean {
return node.kind === SyntaxKind.ExpressionStatement && (<ExpressionStatement>node).expression.kind === SyntaxKind.StringLiteral;
}
export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile) {
// If parameter/type parameter, the prev token trailing comments are part of this node too
if (node.kind === SyntaxKind.Parameter || node.kind === SyntaxKind.TypeParameter) {
// e.g. (/** blah */ a, /** blah */ b);
// e.g.: (
// /** blah */ a,
// /** blah */ b);
return concatenate(
getTrailingCommentRanges(sourceFileOfNode.text, node.pos),
getLeadingCommentRanges(sourceFileOfNode.text, node.pos));
}
else {
return getLeadingCommentRanges(sourceFileOfNode.text, node.pos);
}
}
export function getJsDocComments(node: Node, sourceFileOfNode: SourceFile) {
return filter(getLeadingCommentRangesOfNode(node, sourceFileOfNode), isJsDocComment);
function isJsDocComment(comment: CommentRange) {
// True if the comment starts with '/**' but not if it is '/**/'
return sourceFileOfNode.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk &&
sourceFileOfNode.text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk &&
sourceFileOfNode.text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash;
}
}
export let fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*<reference\s+path\s*=\s*)('|")(.+?)\2.*?\/>/
// 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);
}
}
}
/* @internal */
export function isVariableLike(node: Node): boolean {
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): boolean {
if (node) {
switch (node.kind) {
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return true;
}
}
return false;
}
export function isFunctionLike(node: Node): boolean {
if (node) {
switch (node.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:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionDeclaration:
return true;
}
}
return false;
}
export function isFunctionBlock(node: Node) {
return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent);
}
export function isObjectLiteralMethod(node: Node) {
return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression;
}
export function getContainingFunction(node: Node): FunctionLikeDeclaration {
while (true) {
node = node.parent;
if (!node || isFunctionLike(node)) {
return <FunctionLikeDeclaration>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 (node.parent.parent.kind === SyntaxKind.ClassDeclaration) {
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.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.EnumDeclaration:
case SyntaxKind.SourceFile:
return node;
}
}
}
export function getSuperContainer(node: Node, includeFunctions: boolean): Node {
while (true) {
node = node.parent;
if (!node) return node;
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 'super' container.
// A computed property name in a class needs to be a super container
// so that we can error on it.
if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) {
return node;
}
// If this is a computed property, then the parent should not
// make it a super 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 super container, the reference must be in
// the *body* of the container.
node = node.parent;
break;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
if (!includeFunctions) {
continue;
}
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return node;
}
}
}
export function getInvokedExpression(node: CallLikeExpression): Expression {
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
return (<TaggedTemplateExpression>node).tag;
}
// Will either be a CallExpression or NewExpression.
return (<CallExpression>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.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 && node.parent.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 && node.parent.kind === SyntaxKind.ClassDeclaration;
}
return false;
}
export function nodeIsDecorated(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ClassDeclaration:
if (node.decorators) {
return true;
}
return false;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.Parameter:
if (node.decorators) {
return true;
}
return false;
case SyntaxKind.GetAccessor:
if ((<FunctionLikeDeclaration>node).body && node.decorators) {
return true;
}
return false;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.SetAccessor:
if ((<FunctionLikeDeclaration>node).body && node.decorators) {
return true;
}
return false;
}
return false;
}
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);
}
return false;
}
export function nodeOrChildIsDecorated(node: Node): boolean {
return nodeIsDecorated(node) || childIsDecorated(node);
}
export function isExpression(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.TypeAssertionExpression:
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.SpreadElementExpression:
case SyntaxKind.TemplateExpression:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.OmittedExpression:
return true;
case SyntaxKind.QualifiedName:
while (node.parent.kind === SyntaxKind.QualifiedName) {
node = node.parent;
}
return node.parent.kind === SyntaxKind.TypeQuery;
case SyntaxKind.Identifier:
if (node.parent.kind === SyntaxKind.TypeQuery) {
return true;
}
// fall through
case SyntaxKind.NumericLiteral:
case SyntaxKind.StringLiteral:
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.iterator === 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:
return node === (<TypeAssertion>parent).expression;
case SyntaxKind.TemplateSpan:
return node === (<TemplateSpan>parent).expression;
case SyntaxKind.ComputedPropertyName:
return node === (<ComputedPropertyName>parent).expression;
default:
if (isExpression(parent)) {
return true;
}
}
}
return false;
}
export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) {
let 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) {
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind !== SyntaxKind.ExternalModuleReference;
}
export function getExternalModuleName(node: Node): Expression {
if (node.kind === SyntaxKind.ImportDeclaration) {
return (<ImportDeclaration>node).moduleSpecifier;
}
if (node.kind === SyntaxKind.ImportEqualsDeclaration) {
let reference = (<ImportEqualsDeclaration>node).moduleReference;
if (reference.kind === SyntaxKind.ExternalModuleReference) {
return (<ExternalModuleReference>reference).expression;
}
}
if (node.kind === SyntaxKind.ExportDeclaration) {
return (<ExportDeclaration>node).moduleSpecifier;
}
}
export function hasDotDotDotToken(node: Node) {
return node && node.kind === SyntaxKind.Parameter && (<ParameterDeclaration>node).dotDotDotToken !== undefined;
}
export function hasQuestionToken(node: Node) {
if (node) {
switch (node.kind) {
case SyntaxKind.Parameter:
return (<ParameterDeclaration>node).questionToken !== undefined;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
return (<MethodDeclaration>node).questionToken !== undefined;
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return (<PropertyDeclaration>node).questionToken !== undefined;
}
}
return false;
}
export function hasRestParameters(s: SignatureDeclaration): boolean {
return s.parameters.length > 0 && s.parameters[s.parameters.length - 1].dotDotDotToken !== undefined;
}
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 isTemplateLiteralKind(kind: SyntaxKind): boolean {
return SyntaxKind.FirstTemplateToken <= kind && kind <= SyntaxKind.LastTemplateToken;
}
export function isBindingPattern(node: Node) {
return !!node && (node.kind === SyntaxKind.ArrayBindingPattern || node.kind === SyntaxKind.ObjectBindingPattern);
}
export function isInAmbientContext(node: Node): boolean {
while (node) {
if (node.flags & (NodeFlags.Ambient | NodeFlags.DeclarationFile)) {
return true;
}
node = node.parent;
}
return false;
}
export function isDeclaration(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ArrowFunction:
case SyntaxKind.BindingElement:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.Constructor:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.EnumMember:
case SyntaxKind.ExportSpecifier:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.GetAccessor:
case SyntaxKind.ImportClause:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.NamespaceImport:
case SyntaxKind.Parameter:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.SetAccessor:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.TypeParameter:
case SyntaxKind.VariableDeclaration:
return true;
}
return false;
}
export function isStatement(n: Node): boolean {
switch (n.kind) {
case SyntaxKind.BreakStatement:
case SyntaxKind.ContinueStatement:
case SyntaxKind.DebuggerStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.ExpressionStatement:
case SyntaxKind.EmptyStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.ForStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.LabeledStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.ThrowKeyword:
case SyntaxKind.TryStatement:
case SyntaxKind.VariableStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.WithStatement:
case SyntaxKind.ExportAssignment:
return true;
default:
return false;
}
}
export function isClassElement(n: Node): boolean {
switch (n.kind) {
case SyntaxKind.Constructor:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.IndexSignature:
return true;
default:
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;
}
let 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;
}
// 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 = ...
// export default ...
export function isAliasSymbolDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name ||
node.kind === SyntaxKind.NamespaceImport ||
node.kind === SyntaxKind.ImportSpecifier ||
node.kind === SyntaxKind.ExportSpecifier ||
node.kind === SyntaxKind.ExportAssignment && (<ExportAssignment>node).expression.kind === SyntaxKind.Identifier;
}
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration) {
let heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
return heritageClause && heritageClause.types.length > 0 ? heritageClause.types[0] : undefined;
}
export function getClassImplementsHeritageClauseElements(node: ClassDeclaration) {
let heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ImplementsKeyword);
return heritageClause ? heritageClause.types : undefined;
}
export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration) {
let heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword);
return heritageClause ? heritageClause.types : undefined;
}
export function getHeritageClause(clauses: NodeArray<HeritageClause>, kind: SyntaxKind) {
if (clauses) {
for (let clause of clauses) {
if (clause.token === kind) {
return clause;
}
}
}
return undefined;
}
export function tryResolveScriptReference(host: ScriptReferenceHost, sourceFile: SourceFile, reference: FileReference) {
if (!host.getCompilerOptions().noResolve) {
let referenceFileName = isRootedDiskPath(reference.fileName) ? reference.fileName : combinePaths(getDirectoryPath(sourceFile.fileName), reference.fileName);
referenceFileName = getNormalizedAbsolutePath(referenceFileName, host.getCurrentDirectory());
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 {
let simpleReferenceRegEx = /^\/\/\/\s*<reference\s+/gim;
let isNoDefaultLibRegEx = /^(\/\/\/\s*<reference\s+no-default-lib\s*=\s*)('|")(.+?)\2\s*\/>/gim;
if (simpleReferenceRegEx.exec(comment)) {
if (isNoDefaultLibRegEx.exec(comment)) {
return {
isNoDefaultLib: true
}
}
else {
let matchResult = fullTripleSlashReferencePathRegEx.exec(comment);
if (matchResult) {
let start = commentRange.pos;
let end = commentRange.end;
return {
fileReference: {
pos: start,
end: end,
fileName: matchResult[3]
},
isNoDefaultLib: false
};
}
else {
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;
}
/**
* 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 &&
declaration.name.kind === SyntaxKind.ComputedPropertyName &&
!isWellKnownSymbolSyntactically((<ComputedPropertyName>declaration.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 node.kind === SyntaxKind.PropertyAccessExpression && isESSymbolIdentifier((<PropertyAccessExpression>node).expression);
}
export function getPropertyNameForPropertyNameNode(name: DeclarationName): string {
if (name.kind === SyntaxKind.Identifier || name.kind === SyntaxKind.StringLiteral || name.kind === SyntaxKind.NumericLiteral) {
return (<Identifier | LiteralExpression>name).text;
}
if (name.kind === SyntaxKind.ComputedPropertyName) {
let nameExpression = (<ComputedPropertyName>name).expression;
if (isWellKnownSymbolSyntactically(nameExpression)) {
let rightHandSideName = (<PropertyAccessExpression>nameExpression).name.text;
return getPropertyNameForKnownSymbolName(rightHandSideName);
}
}
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 isModifier(token: SyntaxKind): boolean {
switch (token) {
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.ExportKeyword:
case SyntaxKind.DeclareKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.DefaultKeyword:
return true;
}
return false;
}
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) {
let overlapStart = Math.max(span.start, other.start);
let overlapEnd = Math.min(textSpanEnd(span), textSpanEnd(other));
return overlapStart < overlapEnd;
}
export function textSpanOverlap(span1: TextSpan, span2: TextSpan) {
let overlapStart = Math.max(span1.start, span2.start);
let 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) {
let end = start + length;
return start <= textSpanEnd(span) && end >= span.start;
}
export function textSpanIntersectsWithPosition(span: TextSpan, position: number) {
return position <= textSpanEnd(span) && position >= span.start;
}
export function textSpanIntersection(span1: TextSpan, span2: TextSpan) {
let intersectStart = Math.max(span1.start, span2.start);
let 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.
let 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++) {
let 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 inferrred start.
// Determining the new and old end is also pretty simple. Basically it boils down to paying attention to the
// absolute positions at the asterixes, 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 rahter
// than pusing 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))
// }
let oldStart1 = oldStartN;
let oldEnd1 = oldEndN;
let newEnd1 = newEndN;
let oldStart2 = nextChange.span.start;
let oldEnd2 = textSpanEnd(nextChange.span);
let 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 nodeStartsNewLexicalEnvironment(n: Node): boolean {
return isFunctionLike(n) || n.kind === SyntaxKind.ModuleDeclaration || n.kind === SyntaxKind.SourceFile;
}
export function nodeIsSynthesized(node: Node): boolean {
return node.pos === -1;
}
export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node {
let node = <SynthesizedNode>createNode(kind);
node.pos = -1;
node.end = -1;
node.startsOnNewLine = startsOnNewLine;
return node;
}
// @internal
export function createDiagnosticCollection(): DiagnosticCollection {
let nonFileDiagnostics: Diagnostic[] = [];
let fileDiagnostics: Map<Diagnostic[]> = {};
let diagnosticsModified = false;
let modificationCount = 0;
return {
add,
getGlobalDiagnostics,
getDiagnostics,
getModificationCount
};
function getModificationCount() {
return modificationCount;
}
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] || [];
}
let allDiagnostics: Diagnostic[] = [];
function pushDiagnostic(d: Diagnostic) {
allDiagnostics.push(d);
}
forEach(nonFileDiagnostics, pushDiagnostic);
for (let key in fileDiagnostics) {
if (hasProperty(fileDiagnostics, key)) {
forEach(fileDiagnostics[key], pushDiagnostic);
}
}
return sortAndDeduplicateDiagnostics(allDiagnostics);
}
function sortAndDeduplicate() {
if (!diagnosticsModified) {
return;
}
diagnosticsModified = false;
nonFileDiagnostics = sortAndDeduplicateDiagnostics(nonFileDiagnostics);
for (let key in fileDiagnostics) {
if (hasProperty(fileDiagnostics, key)) {
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.
let escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
let escapedCharsMap: Map<string> = {
"\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));
}
}
function get16BitUnicodeEscapeSequence(charCode: number): string {
let hexCharCode = charCode.toString(16).toUpperCase();
let paddedHexCode = ("0000" + hexCharCode).slice(-4);
return "\\u" + paddedHexCode;
}
let 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(sourceFile: SourceFile, 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;
}
let 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 = "";
let indent = 0;
let lineStart = true;
let lineCount = 0;
let linePos = 0;
function write(s: string) {
if (s && s.length) {
if (lineStart) {
output += getIndentString(indent);
lineStart = false;
}
output += s;
}
}
function rawWrite(s: string) {
if (s !== undefined) {
if (lineStart) {
lineStart = false;
}
output += s;
}
}
function writeLiteral(s: string) {
if (s && s.length) {
write(s);
let lineStartsOfS = computeLineStarts(s);
if (lineStartsOfS.length > 1) {
lineCount = lineCount + lineStartsOfS.length - 1;
linePos = output.length - s.length + lineStartsOfS[lineStartsOfS.length - 1];
}
}
}
function writeLine() {
if (!lineStart) {
output += newLine;
lineCount++;
linePos = output.length;
lineStart = true;
}
}
function writeTextOfNode(sourceFile: SourceFile, node: Node) {
write(getSourceTextOfNodeFromSourceFile(sourceFile, node));
}
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,
};
}
export function getOwnEmitOutputFilePath(sourceFile: SourceFile, host: EmitHost, extension: string) {
let 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 getSourceFilePathInNewDir(sourceFile: SourceFile, host: EmitHost, newDirPath: string) {
let sourceFilePath = getNormalizedAbsolutePath(sourceFile.fileName, host.getCurrentDirectory());
sourceFilePath = sourceFilePath.replace(host.getCommonSourceDirectory(), "");
return combinePaths(newDirPath, sourceFilePath);
}
export function writeFile(host: EmitHost, diagnostics: Diagnostic[], fileName: string, data: string, writeByteOrderMark: boolean) {
host.writeFile(fileName, data, writeByteOrderMark, hostErrorMessage => {
diagnostics.push(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, hostErrorMessage));
});
}
export function getLineOfLocalPosition(currentSourceFile: SourceFile, pos: number) {
return getLineAndCharacterOfPosition(currentSourceFile, 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;
}
});
}
export function shouldEmitToOwnFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean {
if (!isDeclarationFile(sourceFile)) {
if ((isExternalModule(sourceFile) || !compilerOptions.out) && !fileExtensionIs(sourceFile.fileName, ".js")) {
return true;
}
return false;
}
return false;
}
export function getAllAccessorDeclarations(declarations: NodeArray<Declaration>, accessor: AccessorDeclaration) {
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)
&& (member.flags & NodeFlags.Static) === (accessor.flags & NodeFlags.Static)) {
let memberName = getPropertyNameForPropertyNameNode(member.name);
let 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(currentSourceFile: SourceFile, writer: EmitTextWriter, node: TextRange, leadingComments: CommentRange[]) {
// If the leading comments start on different line than the start of node, write new line
if (leadingComments && leadingComments.length && node.pos !== leadingComments[0].pos &&
getLineOfLocalPosition(currentSourceFile, node.pos) !== getLineOfLocalPosition(currentSourceFile, leadingComments[0].pos)) {
writer.writeLine();
}
}
export function emitComments(currentSourceFile: SourceFile, writer: EmitTextWriter, comments: CommentRange[], trailingSeparator: boolean, newLine: string,
writeComment: (currentSourceFile: SourceFile, writer: EmitTextWriter, comment: CommentRange, newLine: string) => void) {
let emitLeadingSpace = !trailingSeparator;
forEach(comments, comment => {
if (emitLeadingSpace) {
writer.write(" ");
emitLeadingSpace = false;
}
writeComment(currentSourceFile, writer, comment, newLine);
if (comment.hasTrailingNewLine) {
writer.writeLine();
}
else if (trailingSeparator) {
writer.write(" ");
}
else {
// Emit leading space to separate comment during next comment emit
emitLeadingSpace = true;
}
});
}
export function writeCommentRange(currentSourceFile: SourceFile, writer: EmitTextWriter, comment: CommentRange, newLine: string) {
if (currentSourceFile.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk) {
let firstCommentLineAndCharacter = getLineAndCharacterOfPosition(currentSourceFile, comment.pos);
let lineCount = getLineStarts(currentSourceFile).length;
let firstCommentLineIndent: number;
for (let pos = comment.pos, currentLine = firstCommentLineAndCharacter.line; pos < comment.end; currentLine++) {
let nextLineStart = (currentLine + 1) === lineCount
? currentSourceFile.text.length + 1
: getStartPositionOfLine(currentLine + 1, currentSourceFile);
if (pos !== comment.pos) {
// If we are not emitting first line, we need to write the spaces to adjust the alignment
if (firstCommentLineIndent === undefined) {
firstCommentLineIndent = calculateIndent(getStartPositionOfLine(firstCommentLineAndCharacter.line, currentSourceFile), comment.pos);
}
// These are number of spaces writer is going to write at current indent
let 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 { }
// }
let spacesToEmit = currentWriterIndentSpacing - firstCommentLineIndent + calculateIndent(pos, nextLineStart);
if (spacesToEmit > 0) {
let numberOfSingleSpacesToEmit = spacesToEmit % getIndentSize();
let 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(pos, nextLineStart);
pos = nextLineStart;
}
}
else {
// Single line comment of style //....
writer.write(currentSourceFile.text.substring(comment.pos, comment.end));
}
function writeTrimmedCurrentLine(pos: number, nextLineStart: number) {
let end = Math.min(comment.end, nextLineStart - 1);
let currentLineText = currentSourceFile.text.substring(pos, end).replace(/^\s+|\s+$/g, '');
if (currentLineText) {
// trimmed forward and ending spaces text
writer.write(currentLineText);
if (end !== comment.end) {
writer.writeLine();
}
}
else {
// Empty string - make sure we write empty line
writer.writeLiteral(newLine);
}
}
function calculateIndent(pos: number, end: number) {
let currentLineIndent = 0;
for (; pos < end && isWhiteSpace(currentSourceFile.text.charCodeAt(pos)); pos++) {
if (currentSourceFile.text.charCodeAt(pos) === CharacterCodes.tab) {
// Tabs = TabSize = indent size and go to next tabStop
currentLineIndent += getIndentSize() - (currentLineIndent % getIndentSize());
}
else {
// Single space
currentLineIndent++;
}
}
return currentLineIndent;
}
}
// Returns false if this heritage clause element's expression contains something unsupported
// (i.e. not a name or dotted name).
export function isSupportedHeritageClauseElement(node: HeritageClauseElement): boolean {
return isSupportedHeritageClauseElementExpression(node.expression);
}
function isSupportedHeritageClauseElementExpression(node: Expression): boolean {
if (node.kind === SyntaxKind.Identifier) {
return true;
}
else if (node.kind === SyntaxKind.PropertyAccessExpression) {
return isSupportedHeritageClauseElementExpression((<PropertyAccessExpression>node).expression);
}
else {
return false;
}
}
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 getLocalSymbolForExportDefault(symbol: Symbol) {
return symbol && symbol.valueDeclaration && (symbol.valueDeclaration.flags & NodeFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
}
}