/// /* @internal */ 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 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 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): 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); } // 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 (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 "[]", where 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 }; } export function getSpanOfTokenAtPosition(sourceFile: SourceFile, pos: number): TextSpan { let scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ true, sourceFile.text, /*onError:*/ undefined, 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 = (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 && (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*/ // 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(body: Block, visitor: (stmt: ReturnStatement) => T): T { return traverse(body); function traverse(node: Node): T { switch (node.kind) { case SyntaxKind.ReturnStatement: return visitor(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 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: 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 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.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.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.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.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 (node).tag; } else if (node.kind === SyntaxKind.Decorator) { return (node).expression; } // Will either be a CallExpression or NewExpression. return (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 (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 (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 ((node).body && node.decorators) { return true; } return false; case SyntaxKind.MethodDeclaration: case SyntaxKind.SetAccessor: if ((node).body && node.decorators) { return true; } return false; } return false; } export function childIsDecorated(node: Node): boolean { switch (node.kind) { case SyntaxKind.ClassDeclaration: return forEach((node).members, nodeOrChildIsDecorated); case SyntaxKind.MethodDeclaration: case SyntaxKind.SetAccessor: return forEach((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 (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 (parent).expression === node; case SyntaxKind.ForStatement: let 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 = parent; return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || forInStatement.expression === node; case SyntaxKind.TypeAssertionExpression: return node === (parent).expression; case SyntaxKind.TemplateSpan: return node === (parent).expression; case SyntaxKind.ComputedPropertyName: return node === (parent).expression; case SyntaxKind.Decorator: return true; 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 && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference; } export function getExternalModuleImportEqualsDeclarationExpression(node: Node) { Debug.assert(isExternalModuleImportEqualsDeclaration(node)); return ((node).moduleReference).expression; } export function isInternalModuleImportEqualsDeclaration(node: Node) { return node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind !== SyntaxKind.ExternalModuleReference; } export function getExternalModuleName(node: Node): Expression { if (node.kind === SyntaxKind.ImportDeclaration) { return (node).moduleSpecifier; } if (node.kind === SyntaxKind.ImportEqualsDeclaration) { let reference = (node).moduleReference; if (reference.kind === SyntaxKind.ExternalModuleReference) { return (reference).expression; } } if (node.kind === SyntaxKind.ExportDeclaration) { return (node).moduleSpecifier; } } export function hasDotDotDotToken(node: Node) { return node && node.kind === SyntaxKind.Parameter && (node).dotDotDotToken !== undefined; } export function hasQuestionToken(node: Node) { if (node) { switch (node.kind) { case SyntaxKind.Parameter: return (node).questionToken !== undefined; case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: return (node).questionToken !== undefined; case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.PropertyAssignment: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: return (node).questionToken !== undefined; } } return false; } export function hasRestParameters(s: SignatureDeclaration): boolean { return s.parameters.length > 0 && lastOrUndefined(s.parameters).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.MethodSignature: 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 ((parent).propertyName) { return true; } } if (isDeclaration(parent)) { return (parent).name === name; } return false; } // An alias symbol is created by one of the following declarations: // import = ... // import from ... // import * as from ... // import { x as } from ... // export { x as } from ... // export = ... // export default ... export function isAliasSymbolDeclaration(node: Node): boolean { return node.kind === SyntaxKind.ImportEqualsDeclaration || node.kind === SyntaxKind.ImportClause && !!(node).name || node.kind === SyntaxKind.NamespaceImport || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.ExportSpecifier || node.kind === SyntaxKind.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, 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*/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., 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((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((node).expression); } export function getPropertyNameForPropertyNameNode(name: DeclarationName): string { if (name.kind === SyntaxKind.Identifier || name.kind === SyntaxKind.StringLiteral || name.kind === SyntaxKind.NumericLiteral) { return (name).text; } if (name.kind === SyntaxKind.ComputedPropertyName) { let nameExpression = (name).expression; if (isWellKnownSymbolSyntactically(nameExpression)) { let rightHandSideName = (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 && (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 isParameterDeclaration(node: VariableLikeDeclaration) { let 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(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 = createNode(kind); node.pos = -1; node.end = -1; node.startsOnNewLine = startsOnNewLine; return node; } export function createSynthesizedNodeArray(): NodeArray { var array = >[]; array.pos = -1; array.end = -1; return array; } export function createDiagnosticCollection(): DiagnosticCollection { let nonFileDiagnostics: Diagnostic[] = []; let fileDiagnostics: Map = {}; 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 = { "\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 + lastOrUndefined(lineStartsOfS); } } } 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((member).body)) { return member; } }); } export function shouldEmitToOwnFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean { if (!isDeclarationFile(sourceFile)) { if ((isExternalModule(sourceFile) || !compilerOptions.out)) { // 1. in-browser single file compilation scenario // 2. non .js file return compilerOptions.separateCompilation || !fileExtensionIs(sourceFile.fileName, ".js"); } return false; } return false; } export function getAllAccessorDeclarations(declarations: NodeArray, 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 = member; } else if (!secondAccessor) { secondAccessor = member; } if (member.kind === SyntaxKind.GetAccessor && !getAccessor) { getAccessor = member; } if (member.kind === SyntaxKind.SetAccessor && !setAccessor) { setAccessor = 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; } } export function modifierToFlag(token: SyntaxKind): NodeFlags { switch (token) { case SyntaxKind.StaticKeyword: return NodeFlags.Static; case SyntaxKind.PublicKeyword: return NodeFlags.Public; case SyntaxKind.ProtectedKeyword: return NodeFlags.Protected; case SyntaxKind.PrivateKeyword: return NodeFlags.Private; case SyntaxKind.ExportKeyword: return NodeFlags.Export; case SyntaxKind.DeclareKeyword: return NodeFlags.Ambient; case SyntaxKind.ConstKeyword: return NodeFlags.Const; case SyntaxKind.DefaultKeyword: return NodeFlags.Default; } return 0; } export function isLeftHandSideExpression(expr: Expression): boolean { if (expr) { switch (expr.kind) { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.NewExpression: case SyntaxKind.CallExpression: case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.ClassExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.Identifier: case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateExpression: case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.ThisKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.SuperKeyword: return true; } } return false; } export function isAssignmentOperator(token: SyntaxKind): boolean { return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment; } // 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 (node.kind === SyntaxKind.PropertyAccessExpression) { return isSupportedExpressionWithTypeArgumentsRest((node).expression); } else { return false; } } export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) { return (node.parent.kind === SyntaxKind.QualifiedName && (node.parent).right === node) || (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).name === node); } export function getLocalSymbolForExportDefault(symbol: Symbol) { return symbol && symbol.valueDeclaration && (symbol.valueDeclaration.flags & NodeFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined; } /** * 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[] { let output: number[] = []; let length = input.length; let leadSurrogate: number = undefined; for (let i = 0; i < length; i++) { let 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; } const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; /** * Converts a string to a base-64 encoded ASCII string. */ export function convertToBase64(input: string): string { var result = ""; let charCodes = getExpandedCharCodes(input); let i = 0; let 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 ouput result += base64Digits.charAt(byte1) + base64Digits.charAt(byte2) + base64Digits.charAt(byte3) + base64Digits.charAt(byte4); i += 3; } return result; } } module ts { export function getDefaultLibFileName(options: CompilerOptions): string { return options.target === ScriptTarget.ES6 ? "lib.es6.d.ts" : "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) { 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); } }