From 5bd49fec1d6e67baf1c13fa348aa2b3bb42b42f8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 11:45:33 -0800 Subject: [PATCH 01/50] Initial entrypoint in SourceFile for the LS to call to peform incremental parsing. Right now the entrypoint just causes a full parse to happen. But the LS code is cleaned up to take advantage of it appropriately. --- src/compiler/parser.ts | 101 +++++++++------ src/compiler/types.ts | 34 ++++- src/harness/fourslash.ts | 6 +- src/harness/harnessLanguageService.ts | 10 +- src/services/breakpoints.ts | 2 +- src/services/formatting.ts | 2 +- src/services/formatting/tokenSpan.ts | 2 +- src/services/navigationBar.ts | 4 +- src/services/outliningElementsCollector.ts | 6 +- src/services/services.ts | 141 ++++++++++----------- src/services/shims.ts | 8 +- src/services/signatureHelp.ts | 4 +- src/services/text.ts | 28 ++-- tests/cases/unittests/incrementalParser.ts | 9 +- 14 files changed, 202 insertions(+), 155 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4f4b7bd96e0..e29fade5453 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1034,10 +1034,13 @@ module ts { export function createSourceFile(filename: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false): SourceFile { var parsingContext: ParsingContext; - var identifiers: Map = {}; + var identifiers: Map; var identifierCount = 0; var nodeCount = 0; var lineStarts: number[]; + var syntacticDiagnostics: Diagnostic[]; + var scanner: Scanner; + var token: SyntaxKind; // Flags that dictate what parsing context we're in. For example: // Whether or not we are in strict parsing mode. All that changes in strict parsing mode is @@ -1085,7 +1088,7 @@ module ts { // Note: it should not be necessary to save/restore these flags during speculative/lookahead // parsing. These context flags are naturally stored and restored through normal recursive // descent parsing and unwinding. - var contextFlags: ParserContextFlags = 0; + var contextFlags: ParserContextFlags; // Whether or not we've had a parse error since creating the last AST node. If we have // encountered an error, it will be stored on the next AST node we create. Parse errors @@ -1114,48 +1117,69 @@ module ts { // // Note: any errors at the end of the file that do not precede a regular node, should get // attached to the EOF token. - var parseErrorBeforeNextFinishedNode = false; + var parseErrorBeforeNextFinishedNode: boolean; - var sourceFile = createNode(SyntaxKind.SourceFile, 0); - if (fileExtensionIs(filename, ".d.ts")) { - sourceFile.flags = NodeFlags.DeclarationFile; - } - sourceFile.end = sourceText.length; - sourceFile.filename = normalizePath(filename); - sourceFile.text = sourceText; + var sourceFile: SourceFile; - sourceFile.getLineAndCharacterFromPosition = getLineAndCharacterFromSourcePosition; - sourceFile.getPositionFromLineAndCharacter = getPositionFromSourceLineAndCharacter; - sourceFile.getLineStarts = getLineStarts; - sourceFile.getSyntacticDiagnostics = getSyntacticDiagnostics; + return parseSourceFile(sourceText, /*textChangeRange:*/ undefined, setParentNodes); - sourceFile.referenceDiagnostics = []; - sourceFile.parseDiagnostics = []; - sourceFile.grammarDiagnostics = []; - sourceFile.semanticDiagnostics = []; + function parseSourceFile(text: string, textChangeRange: TextChangeRange, setParentNodes: boolean): SourceFile { + // Set our initial state before parsing. + sourceText = text; + parsingContext = 0; + identifiers = {}; + lineStarts = undefined; + syntacticDiagnostics = undefined; + contextFlags = 0; + parseErrorBeforeNextFinishedNode = false; - processReferenceComments(); + sourceFile = createNode(SyntaxKind.SourceFile, 0); + sourceFile.referenceDiagnostics = []; + sourceFile.parseDiagnostics = []; + sourceFile.grammarDiagnostics = []; + sourceFile.semanticDiagnostics = []; - // Create and prime the scanner before parsing the source elements. - var scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError); - var token = nextToken(); + // Create and prime the scanner before parsing the source elements. + scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError); + token = nextToken(); - sourceFile.statements = parseList(ParsingContext.SourceElements, /*checkForStrictMode*/ true, parseSourceElement); - Debug.assert(token === SyntaxKind.EndOfFileToken); - sourceFile.endOfFileToken = parseTokenNode(); + sourceFile.flags = fileExtensionIs(filename, ".d.ts") ? NodeFlags.DeclarationFile : 0; + sourceFile.end = sourceText.length; + sourceFile.filename = normalizePath(filename); + sourceFile.text = sourceText; - sourceFile.externalModuleIndicator = getExternalModuleIndicator(); + sourceFile.getLineAndCharacterFromPosition = getLineAndCharacterFromSourcePosition; + sourceFile.getPositionFromLineAndCharacter = getPositionFromSourceLineAndCharacter; + sourceFile.getLineStarts = getLineStarts; + sourceFile.getSyntacticDiagnostics = getSyntacticDiagnostics; + sourceFile.update = update; - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.languageVersion = languageVersion; - sourceFile.identifiers = identifiers; + processReferenceComments(sourceFile); - if (setParentNodes) { - fixupParentReferences(sourceFile); + sourceFile.statements = parseList(ParsingContext.SourceElements, /*checkForStrictMode*/ true, parseSourceElement); + Debug.assert(token === SyntaxKind.EndOfFileToken); + sourceFile.endOfFileToken = parseTokenNode(); + + setExternalModuleIndicator(sourceFile); + + sourceFile.nodeCount = nodeCount; + sourceFile.identifierCount = identifierCount; + sourceFile.languageVersion = languageVersion; + sourceFile.identifiers = identifiers; + + if (setParentNodes) { + fixupParentReferences(sourceFile); + } + + return sourceFile; + } + + function update(newText: string, textChangeRange: TextChangeRange) { + // Don't pass along the text change range for now. We'll pass it along once incremental + // parsing is enabled. + return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); } - return sourceFile; function setContextFlag(val: Boolean, flag: ParserContextFlags) { if (val) { @@ -4467,7 +4491,7 @@ module ts { : parseStatement(); } - function processReferenceComments(): void { + function processReferenceComments(sourceFile: SourceFile): void { var triviaScanner = createScanner(languageVersion, /*skipTrivia*/false, sourceText); var referencedFiles: FileReference[] = []; var amdDependencies: string[] = []; @@ -4523,16 +4547,15 @@ module ts { sourceFile.amdModuleName = amdModuleName; } - function getExternalModuleIndicator() { - return forEach(sourceFile.statements, node => + function setExternalModuleIndicator(sourceFile: SourceFile) { + sourceFile.externalModuleIndicator = forEach(sourceFile.statements, node => node.flags & NodeFlags.Export || node.kind === SyntaxKind.ImportDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference || node.kind === SyntaxKind.ExportAssignment - ? node - : undefined); + ? node + : undefined); } - var syntacticDiagnostics: Diagnostic[]; function getSyntacticDiagnostics() { if (syntacticDiagnostics === undefined) { if (sourceFile.parseDiagnostics.length > 0) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 37a7775491e..07fc909a9aa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -866,9 +866,20 @@ module ts { filename: string; text: string; + getLineAndCharacterFromPosition(position: number): LineAndCharacter; getPositionFromLineAndCharacter(line: number, character: number): number; getLineStarts(): number[]; + + // Updates this source file to represent the 'newText' passed in. The 'textChangeRange' + // parameter indicates what changed between the 'text' that this SourceFile has and the + // 'newText'. + // + // Note: this function mutates nodes from this SourceFile. That means any existing nodes + // from this SourceFile that are being held onto may change as a result (including + // becoming detached from any SourceFile). + update(newText: string, textChangeRange: TextChangeRange): SourceFile; + amdDependencies: string[]; amdModuleName: string; referencedFiles: FileReference[]; @@ -1435,7 +1446,6 @@ module ts { character: number; } - export const enum ScriptTarget { ES3, ES5, @@ -1607,4 +1617,26 @@ module ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; } + + export interface TextChangeRange { + span(): TextSpan; + newLength(): number; + newSpan(): TextSpan; + isUnchanged(): boolean; + } + + export interface TextSpan { + start(): number; + length(): number; + end(): number; + isEmpty(): boolean; + containsPosition(position: number): boolean; + containsTextSpan(span: TextSpan): boolean; + overlapsWith(span: TextSpan): boolean; + overlap(span: TextSpan): TextSpan; + intersectsWithTextSpan(span: TextSpan): boolean; + intersectsWith(start: number, length: number): boolean; + intersectsWithPosition(position: number): boolean; + intersection(span: TextSpan): TextSpan; + } } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 0114d213398..c88364de8b8 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1755,14 +1755,14 @@ module FourSlash { public verifySemanticClassifications(expected: { classificationType: string; text: string }[]) { var actual = this.languageService.getSemanticClassifications(this.activeFile.fileName, - new ts.TextSpan(0, this.activeFile.content.length)); + new ts.TextSpanObject(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); } public verifySyntacticClassifications(expected: { classificationType: string; text: string }[]) { var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName, - new ts.TextSpan(0, this.activeFile.content.length)); + new ts.TextSpanObject(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); } @@ -1796,7 +1796,7 @@ module FourSlash { for (var i = 0; i < spans.length; i++) { var expectedSpan = spans[i]; var actualComment = actual[i]; - var actualCommentSpan = new ts.TextSpan(actualComment.position, actualComment.message.length); + var actualCommentSpan = new ts.TextSpanObject(actualComment.position, actualComment.message.length); if (expectedSpan.start !== actualCommentSpan.start() || expectedSpan.end !== actualCommentSpan.end()) { this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start() + ',' + actualCommentSpan.end() + ')'); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index fe635349ec8..c4a75772529 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -32,8 +32,8 @@ module Harness.LanguageService { // Store edit range + new length of script this.editRanges.push({ length: this.content.length, - textChangeRange: new ts.TextChangeRange( - ts.TextSpan.fromBounds(minChar, limChar), newText.length) + textChangeRange: new ts.TextChangeRangeObject( + ts.TextSpanObject.fromBounds(minChar, limChar), newText.length) }); // Update version # @@ -43,14 +43,14 @@ module Harness.LanguageService { public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { if (startVersion === endVersion) { // No edits! - return ts.TextChangeRange.unchanged; + return ts.TextChangeRangeObject.unchanged; } var initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); var lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); var entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); - return ts.TextChangeRange.collapseChangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); + return ts.TextChangeRangeObject.collapseChangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); } } @@ -126,7 +126,7 @@ module Harness.LanguageService { isOpen: boolean, textChangeRange: ts.TextChangeRange ): ts.SourceFile { - return document.update(scriptSnapshot, version, isOpen, textChangeRange); + return ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, isOpen, textChangeRange); } public releaseDocument(fileName: string, compilationSettings: ts.CompilerOptions): void { diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 3a2cc2ad7e7..4e4ad585503 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -38,7 +38,7 @@ module ts.BreakpointResolver { return spanInNode(tokenAtLocation); function textSpan(startNode: Node, endNode?: Node) { - return TextSpan.fromBounds(startNode.getStart(), (endNode || startNode).getEnd()); + return TextSpanObject.fromBounds(startNode.getStart(), (endNode || startNode).getEnd()); } function spanInNodeIfStartsOnSameLine(node: Node, otherwiseOnNode?: Node): TextSpan { diff --git a/src/services/formatting.ts b/src/services/formatting.ts index 0c2dd4c51dc..0cba03664d4 100644 --- a/src/services/formatting.ts +++ b/src/services/formatting.ts @@ -868,7 +868,7 @@ module ts.formatting { } function newTextChange(start: number, len: number, newText: string): TextChange { - return { span: new TextSpan(start, len), newText } + return { span: new TextSpanObject(start, len), newText } } function recordDelete(start: number, len: number) { diff --git a/src/services/formatting/tokenSpan.ts b/src/services/formatting/tokenSpan.ts index 1d8173a8227..dd328931919 100644 --- a/src/services/formatting/tokenSpan.ts +++ b/src/services/formatting/tokenSpan.ts @@ -16,7 +16,7 @@ /// module ts.formatting { - export class TokenSpan extends TextSpan { + export class TokenSpan extends TextSpanObject { constructor(public kind: SyntaxKind, start: number, length: number) { super(start, length); } diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 29b2db72fc5..cfa3c98beb7 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -462,8 +462,8 @@ module ts.NavigationBar { function getNodeSpan(node: Node) { return node.kind === SyntaxKind.SourceFile - ? TextSpan.fromBounds(node.getFullStart(), node.getEnd()) - : TextSpan.fromBounds(node.getStart(), node.getEnd()); + ? TextSpanObject.fromBounds(node.getFullStart(), node.getEnd()) + : TextSpanObject.fromBounds(node.getStart(), node.getEnd()); } function getTextOfNode(node: Node): string { diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 83eef2f4378..9cbd2a69a38 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -38,8 +38,8 @@ module ts { function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean) { if (hintSpanNode && startElement && endElement) { var span: OutliningSpan = { - textSpan: TextSpan.fromBounds(startElement.pos, endElement.end), - hintSpan: TextSpan.fromBounds(hintSpanNode.getStart(), hintSpanNode.end), + textSpan: TextSpanObject.fromBounds(startElement.pos, endElement.end), + hintSpan: TextSpanObject.fromBounds(hintSpanNode.getStart(), hintSpanNode.end), bannerText: collapseText, autoCollapse: autoCollapse }; @@ -88,7 +88,7 @@ module ts { else { // Block was a standalone block. In this case we want to only collapse // the span of the block, independent of any parent span. - var span = TextSpan.fromBounds(n.getStart(), n.end); + var span = TextSpanObject.fromBounds(n.getStart(), n.end); elements.push({ textSpan: span, hintSpan: span, diff --git a/src/services/services.ts b/src/services/services.ts index 07eafbcbeaf..856e4cb2c9a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -61,10 +61,9 @@ module ts { export interface SourceFile { isOpen: boolean; version: string; + scriptSnapshot: IScriptSnapshot; - getScriptSnapshot(): IScriptSnapshot; getNamedDeclarations(): Declaration[]; - update(scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile; } /** @@ -724,6 +723,7 @@ module ts { public _declarationBrand: any; public filename: string; public text: string; + public scriptSnapshot: IScriptSnapshot; public statements: NodeArray; public endOfFileToken: Node; @@ -734,6 +734,7 @@ module ts { public getPositionFromLineAndCharacter: (line: number, character: number) => number; public getLineStarts: () => number[]; public getSyntacticDiagnostics: () => Diagnostic[]; + public update: (newText: string, textChangeRange: TextChangeRange) => SourceFile; public amdDependencies: string[]; public amdModuleName: string; @@ -754,13 +755,8 @@ module ts { public languageVersion: ScriptTarget; public identifiers: Map; - private scriptSnapshot: IScriptSnapshot; private namedDeclarations: Declaration[]; - public getScriptSnapshot(): IScriptSnapshot { - return this.scriptSnapshot; - } - public getNamedDeclarations() { if (!this.namedDeclarations) { var sourceFile = this; @@ -846,35 +842,6 @@ module ts { return this.namedDeclarations; } - - public update(scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile { - if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { - var oldText = this.scriptSnapshot; - var newText = scriptSnapshot; - - Debug.assert((oldText.getLength() - textChangeRange.span().length() + textChangeRange.newLength()) === newText.getLength()); - - if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - var oldTextPrefix = oldText.getText(0, textChangeRange.span().start()); - var newTextPrefix = newText.getText(0, textChangeRange.span().start()); - Debug.assert(oldTextPrefix === newTextPrefix); - - var oldTextSuffix = oldText.getText(textChangeRange.span().end(), oldText.getLength()); - var newTextSuffix = newText.getText(textChangeRange.newSpan().end(), newText.getLength()); - Debug.assert(oldTextSuffix === newTextSuffix); - } - } - - return SourceFileObject.createSourceFileObject(this.filename, scriptSnapshot, this.languageVersion, version, isOpen); - } - - public static createSourceFileObject(filename: string, scriptSnapshot: IScriptSnapshot, languageVersion: ScriptTarget, version: string, isOpen: boolean) { - var newSourceFile = createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), languageVersion, /*setParentNodes:*/ true); - newSourceFile.version = version; - newSourceFile.isOpen = isOpen; - newSourceFile.scriptSnapshot = scriptSnapshot; - return newSourceFile; - } } export interface Logger { @@ -1646,7 +1613,7 @@ module ts { public getChangeRange(filename: string, lastKnownVersion: string, oldScriptSnapshot: IScriptSnapshot): TextChangeRange { var currentVersion = this.getVersion(filename); if (lastKnownVersion === currentVersion) { - return TextChangeRange.unchanged; // "No changes" + return TextChangeRangeObject.unchanged; // "No changes" } var scriptSnapshot = this.getScriptSnapshot(filename); @@ -1679,25 +1646,17 @@ module ts { var scriptSnapshot = this.hostCache.getScriptSnapshot(filename); var start = new Date().getTime(); - sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true); + sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, ScriptTarget.Latest, version, /*isOpen*/ true, /*setNodeParents;*/ true); this.host.log("SyntaxTreeCache.Initialize: createSourceFile: " + (new Date().getTime() - start)); - - var start = new Date().getTime(); - this.host.log("SyntaxTreeCache.Initialize: fixupParentRefs : " + (new Date().getTime() - start)); } else if (this.currentFileVersion !== version) { var scriptSnapshot = this.hostCache.getScriptSnapshot(filename); - var editRange = this.hostCache.getChangeRange(filename, this.currentFileVersion, this.currentSourceFile.getScriptSnapshot()); + var editRange = this.hostCache.getChangeRange(filename, this.currentFileVersion, this.currentSourceFile.scriptSnapshot); var start = new Date().getTime(); - sourceFile = !editRange - ? createLanguageServiceSourceFile(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true) - : this.currentSourceFile.update(scriptSnapshot, version, /*isOpen*/ true, editRange); + sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, /*isOpen*/ true, editRange); this.host.log("SyntaxTreeCache.Initialize: updateSourceFile: " + (new Date().getTime() - start)); - - var start = new Date().getTime(); - this.host.log("SyntaxTreeCache.Initialize: fixupParentRefs : " + (new Date().getTime() - start)); } if (sourceFile) { @@ -1714,12 +1673,52 @@ module ts { } public getCurrentScriptSnapshot(filename: string): IScriptSnapshot { - return this.getCurrentSourceFile(filename).getScriptSnapshot(); + return this.getCurrentSourceFile(filename).scriptSnapshot; } } - export function createLanguageServiceSourceFile(filename: string, scriptSnapshot: IScriptSnapshot, settings: CompilerOptions, version: string, isOpen: boolean): SourceFile { - return SourceFileObject.createSourceFileObject(filename, scriptSnapshot, settings.target, version, isOpen); + function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean) { + sourceFile.version = version; + sourceFile.isOpen = isOpen; + sourceFile.scriptSnapshot = scriptSnapshot; + } + + export function createLanguageServiceSourceFile(filename: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, isOpen: boolean, setNodeParents: boolean): SourceFile { + var sourceFile = createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), scriptTarget, setNodeParents); + setSourceFileFields(sourceFile, scriptSnapshot, version, isOpen); + return sourceFile; + } + + export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile { + if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { + var oldText = sourceFile.scriptSnapshot; + var newText = scriptSnapshot; + + Debug.assert((oldText.getLength() - textChangeRange.span().length() + textChangeRange.newLength()) === newText.getLength()); + + if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { + var oldTextPrefix = oldText.getText(0, textChangeRange.span().start()); + var newTextPrefix = newText.getText(0, textChangeRange.span().start()); + Debug.assert(oldTextPrefix === newTextPrefix); + + var oldTextSuffix = oldText.getText(textChangeRange.span().end(), oldText.getLength()); + var newTextSuffix = newText.getText(textChangeRange.newSpan().end(), newText.getLength()); + Debug.assert(oldTextSuffix === newTextSuffix); + } + } + + // If we were given a text change range, and our version or open-ness changed, then + // incrementally parse this file. + if (textChangeRange) { + if (version !== sourceFile.version || isOpen != sourceFile.isOpen) { + var newSourceFile = sourceFile.update(scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange); + setSourceFileFields(newSourceFile, scriptSnapshot, version, isOpen); + return newSourceFile; + } + } + + // Otherwise, just create a new source file. + return createLanguageServiceSourceFile(sourceFile.filename, scriptSnapshot, sourceFile.languageVersion, version, isOpen, /*setNodeParents:*/ true); } export function createDocumentRegistry(): DocumentRegistry { @@ -1769,7 +1768,7 @@ module ts { var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true); var entry = lookUp(bucket, filename); if (!entry) { - var sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, compilationSettings, version, isOpen); + var sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, compilationSettings.target, version, isOpen, /*setNodeParents:*/ false); bucket[filename] = entry = { sourceFile: sourceFile, @@ -1797,11 +1796,7 @@ module ts { var entry = lookUp(bucket, filename); Debug.assert(entry !== undefined); - if (entry.sourceFile.isOpen === isOpen && entry.sourceFile.version === version) { - return entry.sourceFile; - } - - entry.sourceFile = entry.sourceFile.update(scriptSnapshot, version, isOpen, textChangeRange); + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, isOpen, textChangeRange); return entry.sourceFile; } @@ -2225,7 +2220,7 @@ module ts { // new text buffer). var textChangeRange: TextChangeRange = null; if (sourceFile.isOpen && isOpen) { - textChangeRange = hostCache.getChangeRange(filename, sourceFile.version, sourceFile.getScriptSnapshot()); + textChangeRange = hostCache.getChangeRange(filename, sourceFile.version, sourceFile.scriptSnapshot); } sourceFile = documentRegistry.updateDocument(sourceFile, filename, compilationSettings, scriptSnapshot, version, isOpen, textChangeRange); @@ -3223,7 +3218,7 @@ module ts { return { kind: ScriptElementKind.unknown, kindModifiers: ScriptElementKindModifier.none, - textSpan: new TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpanObject(node.getStart(), node.getWidth()), displayParts: typeToDisplayParts(typeInfoResolver, type, getContainerNode(node)), documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined }; @@ -3237,7 +3232,7 @@ module ts { return { kind: displayPartsDocumentationsAndKind.symbolKind, kindModifiers: getSymbolModifiers(symbol), - textSpan: new TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpanObject(node.getStart(), node.getWidth()), displayParts: displayPartsDocumentationsAndKind.displayParts, documentation: displayPartsDocumentationsAndKind.documentation }; @@ -3248,7 +3243,7 @@ module ts { function getDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo { return { fileName: node.getSourceFile().filename, - textSpan: TextSpan.fromBounds(node.getStart(), node.getEnd()), + textSpan: TextSpanObject.fromBounds(node.getStart(), node.getEnd()), kind: symbolKind, name: symbolName, containerKind: undefined, @@ -3325,7 +3320,7 @@ module ts { if (referenceFile) { return [{ fileName: referenceFile.filename, - textSpan: TextSpan.fromBounds(0, 0), + textSpan: TextSpanObject.fromBounds(0, 0), kind: ScriptElementKind.scriptElement, name: comment.filename, containerName: undefined, @@ -3516,7 +3511,7 @@ module ts { if (shouldHighlightNextKeyword) { result.push({ fileName: filename, - textSpan: TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), + textSpan: TextSpanObject.fromBounds(elseKeyword.getStart(), ifKeyword.end), isWriteAccess: false }); i++; // skip the next keyword @@ -4208,7 +4203,7 @@ module ts { (findInComments && isInComment(position))) { result.push({ fileName: sourceFile.filename, - textSpan: new TextSpan(position, searchText.length), + textSpan: new TextSpanObject(position, searchText.length), isWriteAccess: false }); } @@ -4591,7 +4586,7 @@ module ts { return { fileName: node.getSourceFile().filename, - textSpan: TextSpan.fromBounds(start, end), + textSpan: TextSpanObject.fromBounds(start, end), isWriteAccess: isWriteAccess(node) }; } @@ -4647,7 +4642,7 @@ module ts { kindModifiers: getNodeModifiers(declaration), matchKind: MatchKind[matchKind], fileName: filename, - textSpan: TextSpan.fromBounds(declaration.getStart(), declaration.getEnd()), + textSpan: TextSpanObject.fromBounds(declaration.getStart(), declaration.getEnd()), // TODO(jfreeman): What should be the containerName when the container has a computed name? containerName: container && container.name ? (container.name).text : "", containerKind: container && container.name ? getNodeKind(container) : "" @@ -4924,7 +4919,7 @@ module ts { } } - return TextSpan.fromBounds(nodeForStartPos.getStart(), node.getEnd()); + return TextSpanObject.fromBounds(nodeForStartPos.getStart(), node.getEnd()); } function getBreakpointStatementAtPosition(filename: string, position: number) { @@ -5001,7 +4996,7 @@ module ts { var type = classifySymbol(symbol, getMeaningFromLocation(node)); if (type) { result.push({ - textSpan: new TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpanObject(node.getStart(), node.getWidth()), classificationType: type }); } @@ -5027,7 +5022,7 @@ module ts { var width = comment.end - comment.pos; if (span.intersectsWith(comment.pos, width)) { result.push({ - textSpan: new TextSpan(comment.pos, width), + textSpan: new TextSpanObject(comment.pos, width), classificationType: ClassificationTypeNames.comment }); } @@ -5040,7 +5035,7 @@ module ts { var type = classifyTokenType(token); if (type) { result.push({ - textSpan: new TextSpan(token.getStart(), token.getWidth()), + textSpan: new TextSpanObject(token.getStart(), token.getWidth()), classificationType: type }); } @@ -5168,8 +5163,8 @@ module ts { var current = childNodes[i]; if (current.kind === matchKind) { - var range1 = new TextSpan(token.getStart(sourceFile), token.getWidth(sourceFile)); - var range2 = new TextSpan(current.getStart(sourceFile), current.getWidth(sourceFile)); + var range1 = new TextSpanObject(token.getStart(sourceFile), token.getWidth(sourceFile)); + var range2 = new TextSpanObject(current.getStart(sourceFile), current.getWidth(sourceFile)); // We want to order the braces when we return the result. if (range1.start() < range2.start()) { @@ -5420,7 +5415,7 @@ module ts { if (kind) { return getRenameInfo(symbol.name, typeInfoResolver.getFullyQualifiedName(symbol), kind, getSymbolModifiers(symbol), - new TextSpan(node.getStart(), node.getWidth())); + new TextSpanObject(node.getStart(), node.getWidth())); } } } diff --git a/src/services/shims.ts b/src/services/shims.ts index 5556743830f..1bafbafa005 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -328,8 +328,8 @@ module ts { } var decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded); - return new TextChangeRange( - new TextSpan(decoded.span.start, decoded.span.length), decoded.newLength); + return new TextChangeRangeObject( + new TextSpanObject(decoded.span.start, decoded.span.length), decoded.newLength); } } @@ -510,7 +510,7 @@ module ts { return this.forwardJSONCall( "getSyntacticClassifications('" + fileName + "', " + start + ", " + length + ")", () => { - var classifications = this.languageService.getSyntacticClassifications(fileName, new TextSpan(start, length)); + var classifications = this.languageService.getSyntacticClassifications(fileName, new TextSpanObject(start, length)); return classifications; }); } @@ -519,7 +519,7 @@ module ts { return this.forwardJSONCall( "getSemanticClassifications('" + fileName + "', " + start + ", " + length + ")", () => { - var classifications = this.languageService.getSemanticClassifications(fileName, new TextSpan(start, length)); + var classifications = this.languageService.getSemanticClassifications(fileName, new TextSpanObject(start, length)); return classifications; }); } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 408fe60a6cd..9291bd0d685 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -367,7 +367,7 @@ module ts.SignatureHelp { // but not including parentheses) var applicableSpanStart = argumentsList.getFullStart(); var applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); - return new TextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + return new TextSpanObject(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression): TextSpan { @@ -391,7 +391,7 @@ module ts.SignatureHelp { } } - return new TextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + return new TextSpanObject(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } function getContainingArgumentInfo(node: Node): ArgumentListInfo { diff --git a/src/services/text.ts b/src/services/text.ts index fd768785dea..85465ffdf46 100644 --- a/src/services/text.ts +++ b/src/services/text.ts @@ -1,5 +1,5 @@ module ts { - export class TextSpan { + export class TextSpanObject { private _start: number; private _length: number; @@ -49,7 +49,7 @@ module ts { * @param span The span to check. */ public containsTextSpan(span: TextSpan): boolean { - return span._start >= this._start && span.end() <= this.end(); + return span.start() >= this._start && span.end() <= this.end(); } /** @@ -59,7 +59,7 @@ module ts { * @param span The span to check. */ public overlapsWith(span: TextSpan): boolean { - var overlapStart = Math.max(this._start, span._start); + var overlapStart = Math.max(this._start, span.start()); var overlapEnd = Math.min(this.end(), span.end()); return overlapStart < overlapEnd; @@ -70,11 +70,11 @@ module ts { * @param span The span to check. */ public overlap(span: TextSpan): TextSpan { - var overlapStart = Math.max(this._start, span._start); + var overlapStart = Math.max(this._start, span.start()); var overlapEnd = Math.min(this.end(), span.end()); if (overlapStart < overlapEnd) { - return TextSpan.fromBounds(overlapStart, overlapEnd); + return TextSpanObject.fromBounds(overlapStart, overlapEnd); } return undefined; @@ -87,7 +87,7 @@ module ts { * @param The span to check. */ public intersectsWithTextSpan(span: TextSpan): boolean { - return span._start <= this.end() && span.end() >= this._start; + return span.start() <= this.end() && span.end() >= this._start; } public intersectsWith(start: number, length: number): boolean { @@ -110,11 +110,11 @@ module ts { * @param span The span to check. */ public intersection(span: TextSpan): TextSpan { - var intersectStart = Math.max(this._start, span._start); + var intersectStart = Math.max(this._start, span.start()); var intersectEnd = Math.min(this.end(), span.end()); if (intersectStart <= intersectEnd) { - return TextSpan.fromBounds(intersectStart, intersectEnd); + return TextSpanObject.fromBounds(intersectStart, intersectEnd); } return undefined; @@ -127,12 +127,12 @@ module ts { public static fromBounds(start: number, end: number): TextSpan { Debug.assert(start >= 0); Debug.assert(end - start >= 0); - return new TextSpan(start, end - start); + return new TextSpanObject(start, end - start); } } - export class TextChangeRange { - public static unchanged = new TextChangeRange(new TextSpan(0, 0), 0); + export class TextChangeRangeObject implements TextChangeRange { + public static unchanged = new TextChangeRangeObject(new TextSpanObject(0, 0), 0); private _span: TextSpan; private _newLength: number; @@ -162,7 +162,7 @@ module ts { } public newSpan(): TextSpan { - return new TextSpan(this.span().start(), this.newLength()); + return new TextSpanObject(this.span().start(), this.newLength()); } public isUnchanged(): boolean { @@ -179,7 +179,7 @@ module ts { */ public static collapseChangesAcrossMultipleVersions(changes: TextChangeRange[]): TextChangeRange { if (changes.length === 0) { - return TextChangeRange.unchanged; + return TextChangeRangeObject.unchanged; } if (changes.length === 1) { @@ -290,7 +290,7 @@ module ts { newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)); } - return new TextChangeRange(TextSpan.fromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); + return new TextChangeRangeObject(TextSpanObject.fromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); } } } \ No newline at end of file diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index f1e59809400..9bd9df5deca 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -6,7 +6,7 @@ module ts { var contents = text.getText(0, text.getLength()); var newContents = contents.substr(0, start) + newText + contents.substring(start + length); - return { text: ScriptSnapshot.fromString(newContents), textChangeRange: new TextChangeRange(new TextSpan(start, length), newText.length) } + return { text: ScriptSnapshot.fromString(newContents), textChangeRange: new TextChangeRangeObject(new TextSpanObject(start, length), newText.length) } } function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { @@ -18,10 +18,7 @@ module ts { } function createTree(text: IScriptSnapshot, version: string) { - var options: CompilerOptions = {}; - options.target = ScriptTarget.ES5; - - return createLanguageServiceSourceFile(/*fileName:*/ "", text, options, version, /*isOpen:*/ true) + return createLanguageServiceSourceFile(/*fileName:*/ "", text, ScriptTarget.Latest, version, /*isOpen:*/ true, /*setNodeParents:*/ true) } // NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new @@ -38,7 +35,7 @@ module ts { Utils.assertInvariants(newTree, /*parent:*/ undefined); // Create a tree for the new text, in an incremental fashion. - var incrementalNewTree = oldTree.update(newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange); + var incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange); Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); // We should get the same tree when doign a full or incremental parse. From 48765ec9041b89c640652b11d47ddb2df5430f7b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 12:18:31 -0800 Subject: [PATCH 02/50] Update comment. --- src/compiler/types.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 07fc909a9aa..3bb937f6e69 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -871,13 +871,15 @@ module ts { getPositionFromLineAndCharacter(line: number, character: number): number; getLineStarts(): number[]; - // Updates this source file to represent the 'newText' passed in. The 'textChangeRange' - // parameter indicates what changed between the 'text' that this SourceFile has and the - // 'newText'. + // Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter + // indicates what changed between the 'text' that this SourceFile has and the 'newText'. + // The SourceFile will be created with the compiler attempting to reuse as many nodes from + // this file as possible. // // Note: this function mutates nodes from this SourceFile. That means any existing nodes // from this SourceFile that are being held onto may change as a result (including - // becoming detached from any SourceFile). + // becoming detached from any SourceFile). It is recommended that this SourceFile not + // be used once 'update' is called on it. update(newText: string, textChangeRange: TextChangeRange): SourceFile; amdDependencies: string[]; From f400e5955af718a1ebb50745d629dcf1500ac7f3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 13:19:01 -0800 Subject: [PATCH 03/50] Don't call into the incremental parser for now. Return a tree if the textChangeRange is empty. --- src/compiler/parser.ts | 6 +++++- src/services/services.ts | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c8149a6cc95..7f6918c54fc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1180,12 +1180,16 @@ module ts { } function update(newText: string, textChangeRange: TextChangeRange) { + if (textChangeRange.isUnchanged()) { + // if the text didn't change, then we can just return our current source file as-is. + return sourceFile; + } + // Don't pass along the text change range for now. We'll pass it along once incremental // parsing is enabled. return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); } - function setContextFlag(val: Boolean, flag: ParserContextFlags) { if (val) { contextFlags |= flag; diff --git a/src/services/services.ts b/src/services/services.ts index 856e4cb2c9a..93545fdaf8d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1711,9 +1711,10 @@ module ts { // incrementally parse this file. if (textChangeRange) { if (version !== sourceFile.version || isOpen != sourceFile.isOpen) { - var newSourceFile = sourceFile.update(scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange); - setSourceFileFields(newSourceFile, scriptSnapshot, version, isOpen); - return newSourceFile; + // Once incremental parsing is ready, then just call into this function. + // var newSourceFile = sourceFile.update(scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange); + // setSourceFileFields(newSourceFile, scriptSnapshot, version, isOpen); + // return newSourceFile; } } From c2d4cd588797d3505321fc911f02dff4d76b5fd4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 13:45:08 -0800 Subject: [PATCH 04/50] Move TextSpan into the compiler layer. --- src/compiler/parser.ts | 2 + src/compiler/types.ts | 383 +++++++++++++++------ src/harness/fourslash.ts | 6 +- src/harness/harnessLanguageService.ts | 2 +- src/services/breakpoints.ts | 2 +- src/services/formatting.ts | 2 +- src/services/formatting/tokenSpan.ts | 5 - src/services/navigationBar.ts | 4 +- src/services/outliningElementsCollector.ts | 6 +- src/services/services.ts | 30 +- src/services/shims.ts | 6 +- src/services/signatureHelp.ts | 4 +- src/services/text.ts | 138 +------- tests/cases/unittests/incrementalParser.ts | 2 +- 14 files changed, 307 insertions(+), 285 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 7f6918c54fc..57300aa475c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1185,6 +1185,8 @@ module ts { return sourceFile; } + + // Don't pass along the text change range for now. We'll pass it along once incremental // parsing is enabled. return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3bb937f6e69..48538173e67 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -279,18 +279,18 @@ module ts { } export const enum NodeFlags { - Export = 0x00000001, // Declarations - Ambient = 0x00000002, // Declarations - Public = 0x00000010, // Property/Method - Private = 0x00000020, // Property/Method - Protected = 0x00000040, // Property/Method - Static = 0x00000080, // Property/Method - MultiLine = 0x00000100, // Multi-line array or object literal - Synthetic = 0x00000200, // Synthetic node (for full fidelity) - DeclarationFile = 0x00000400, // Node is a .d.ts file - Let = 0x00000800, // Variable declaration - Const = 0x00001000, // Variable declaration - OctalLiteral = 0x00002000, + Export = 0x00000001, // Declarations + Ambient = 0x00000002, // Declarations + Public = 0x00000010, // Property/Method + Private = 0x00000020, // Property/Method + Protected = 0x00000040, // Property/Method + Static = 0x00000080, // Property/Method + MultiLine = 0x00000100, // Multi-line array or object literal + Synthetic = 0x00000200, // Synthetic node (for full fidelity) + DeclarationFile = 0x00000400, // Node is a .d.ts file + Let = 0x00000800, // Variable declaration + Const = 0x00001000, // Variable declaration + OctalLiteral = 0x00002000, Modifier = Export | Ambient | Public | Private | Protected | Static, AccessibilityModifier = Public | Private | Protected, @@ -595,7 +595,7 @@ module ts { export interface VoidExpression extends UnaryExpression { expression: UnaryExpression; } - + export interface YieldExpression extends Expression { asteriskToken?: Node; expression: Expression; @@ -871,7 +871,7 @@ module ts { getPositionFromLineAndCharacter(line: number, character: number): number; getLineStarts(): number[]; - // Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter + // Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter // indicates what changed between the 'text' that this SourceFile has and the 'newText'. // The SourceFile will be created with the compiler attempting to reuse as many nodes from // this file as possible. @@ -1032,25 +1032,25 @@ module ts { } export const enum TypeFormatFlags { - None = 0x00000000, - WriteArrayAsGenericType = 0x00000001, // Write Array instead T[] - UseTypeOfFunction = 0x00000002, // Write typeof instead of function type literal - NoTruncation = 0x00000004, // Don't truncate typeToString result - WriteArrowStyleSignature = 0x00000008, // Write arrow style signature - WriteOwnNameForAnyLike = 0x00000010, // Write symbol's own name instead of 'any' for any like types (eg. unknown, __resolving__ etc) - WriteTypeArgumentsOfSignature = 0x00000020, // Write the type arguments instead of type parameters of the signature - InElementType = 0x00000040, // Writing an array or union element type + None = 0x00000000, + WriteArrayAsGenericType = 0x00000001, // Write Array instead T[] + UseTypeOfFunction = 0x00000002, // Write typeof instead of function type literal + NoTruncation = 0x00000004, // Don't truncate typeToString result + WriteArrowStyleSignature = 0x00000008, // Write arrow style signature + WriteOwnNameForAnyLike = 0x00000010, // Write symbol's own name instead of 'any' for any like types (eg. unknown, __resolving__ etc) + WriteTypeArgumentsOfSignature = 0x00000020, // Write the type arguments instead of type parameters of the signature + InElementType = 0x00000040, // Writing an array or union element type } export const enum SymbolFormatFlags { - None = 0x00000000, - WriteTypeParametersOrArguments = 0x00000001, // Write symbols's type argument if it is instantiated symbol - // eg. class C { p: T } <-- Show p as C.p here - // var a: C; - // var p = a.p; <--- Here p is property of C so show it as C.p instead of just C.p - UseOnlyExternalAliasing = 0x00000002, // Use only external alias information to get the symbol name in the given context - // eg. module m { export class c { } } import x = m.c; - // When this flag is specified m.c will be used to refer to the class instead of alias symbol x + None = 0x00000000, + WriteTypeParametersOrArguments = 0x00000001, // Write symbols's type argument if it is instantiated symbol + // eg. class C { p: T } <-- Show p as C.p here + // var a: C; + // var p = a.p; <--- Here p is property of C so show it as C.p instead of just C.p + UseOnlyExternalAliasing = 0x00000002, // Use only external alias information to get the symbol name in the given context + // eg. module m { export class c { } } import x = m.c; + // When this flag is specified m.c will be used to refer to the class instead of alias symbol x } export const enum SymbolAccessibility { @@ -1094,45 +1094,45 @@ module ts { export const enum SymbolFlags { FunctionScopedVariable = 0x00000001, // Variable (var) or parameter - BlockScopedVariable = 0x00000002, // A block-scoped variable (let or const) - Property = 0x00000004, // Property or enum member - EnumMember = 0x00000008, // Enum member - Function = 0x00000010, // Function - Class = 0x00000020, // Class - Interface = 0x00000040, // Interface - ConstEnum = 0x00000080, // Const enum - RegularEnum = 0x00000100, // Enum - ValueModule = 0x00000200, // Instantiated module - NamespaceModule = 0x00000400, // Uninstantiated module - TypeLiteral = 0x00000800, // Type Literal - ObjectLiteral = 0x00001000, // Object Literal - Method = 0x00002000, // Method - Constructor = 0x00004000, // Constructor - GetAccessor = 0x00008000, // Get accessor - SetAccessor = 0x00010000, // Set accessor - Signature = 0x00020000, // Call, construct, or index signature - TypeParameter = 0x00040000, // Type parameter - TypeAlias = 0x00080000, // Type alias + BlockScopedVariable = 0x00000002, // A block-scoped variable (let or const) + Property = 0x00000004, // Property or enum member + EnumMember = 0x00000008, // Enum member + Function = 0x00000010, // Function + Class = 0x00000020, // Class + Interface = 0x00000040, // Interface + ConstEnum = 0x00000080, // Const enum + RegularEnum = 0x00000100, // Enum + ValueModule = 0x00000200, // Instantiated module + NamespaceModule = 0x00000400, // Uninstantiated module + TypeLiteral = 0x00000800, // Type Literal + ObjectLiteral = 0x00001000, // Object Literal + Method = 0x00002000, // Method + Constructor = 0x00004000, // Constructor + GetAccessor = 0x00008000, // Get accessor + SetAccessor = 0x00010000, // Set accessor + Signature = 0x00020000, // Call, construct, or index signature + TypeParameter = 0x00040000, // Type parameter + TypeAlias = 0x00080000, // Type alias // Export markers (see comment in declareModuleMember in binder) - ExportValue = 0x00100000, // Exported value marker - ExportType = 0x00200000, // Exported type marker - ExportNamespace = 0x00400000, // Exported namespace marker - Import = 0x00800000, // Import - Instantiated = 0x01000000, // Instantiated symbol - Merged = 0x02000000, // Merged symbol (created during program binding) - Transient = 0x04000000, // Transient symbol (created during type check) - Prototype = 0x08000000, // Prototype property (no source representation) - UnionProperty = 0x10000000, // Property in union type - Optional = 0x20000000, // Optional property + ExportValue = 0x00100000, // Exported value marker + ExportType = 0x00200000, // Exported type marker + ExportNamespace = 0x00400000, // Exported namespace marker + Import = 0x00800000, // Import + Instantiated = 0x01000000, // Instantiated symbol + Merged = 0x02000000, // Merged symbol (created during program binding) + Transient = 0x04000000, // Transient symbol (created during type check) + Prototype = 0x08000000, // Prototype property (no source representation) + UnionProperty = 0x10000000, // Property in union type + Optional = 0x20000000, // Optional property - Enum = RegularEnum | ConstEnum, - Variable = FunctionScopedVariable | BlockScopedVariable, - Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor, - Type = Class | Interface | Enum | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias, + Enum = RegularEnum | ConstEnum, + Variable = FunctionScopedVariable | BlockScopedVariable, + Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor, + Type = Class | Interface | Enum | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias, Namespace = ValueModule | NamespaceModule, - Module = ValueModule | NamespaceModule, - Accessor = GetAccessor | SetAccessor, + Module = ValueModule | NamespaceModule, + Accessor = GetAccessor | SetAccessor, // Variables can be redeclared, but can not redeclare a block-scoped declaration with the // same name, or any other value that is not a variable, e.g. ValueModule or Class @@ -1142,34 +1142,34 @@ module ts { // they can not merge with anything in the value space BlockScopedVariableExcludes = Value, - ParameterExcludes = Value, - PropertyExcludes = Value, - EnumMemberExcludes = Value, - FunctionExcludes = Value & ~(Function | ValueModule), - ClassExcludes = (Value | Type) & ~ValueModule, - InterfaceExcludes = Type & ~Interface, - RegularEnumExcludes = (Value | Type) & ~(RegularEnum | ValueModule), // regular enums merge only with regular enums and modules - ConstEnumExcludes = (Value | Type) & ~ConstEnum, // const enums merge only with const enums - ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule), + ParameterExcludes = Value, + PropertyExcludes = Value, + EnumMemberExcludes = Value, + FunctionExcludes = Value & ~(Function | ValueModule), + ClassExcludes = (Value | Type) & ~ValueModule, + InterfaceExcludes = Type & ~Interface, + RegularEnumExcludes = (Value | Type) & ~(RegularEnum | ValueModule), // regular enums merge only with regular enums and modules + ConstEnumExcludes = (Value | Type) & ~ConstEnum, // const enums merge only with const enums + ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule), NamespaceModuleExcludes = 0, - MethodExcludes = Value & ~Method, - GetAccessorExcludes = Value & ~SetAccessor, - SetAccessorExcludes = Value & ~GetAccessor, - TypeParameterExcludes = Type & ~TypeParameter, - TypeAliasExcludes = Type, - ImportExcludes = Import, // Imports collide with all other imports with the same name + MethodExcludes = Value & ~Method, + GetAccessorExcludes = Value & ~SetAccessor, + SetAccessorExcludes = Value & ~GetAccessor, + TypeParameterExcludes = Type & ~TypeParameter, + TypeAliasExcludes = Type, + ImportExcludes = Import, // Imports collide with all other imports with the same name ModuleMember = Variable | Function | Class | Interface | Enum | Module | TypeAlias | Import, ExportHasLocal = Function | Class | Enum | ValueModule, - HasLocals = Function | Module | Method | Constructor | Accessor | Signature, + HasLocals = Function | Module | Method | Constructor | Accessor | Signature, HasExports = Class | Enum | Module, HasMembers = Class | Interface | TypeLiteral | ObjectLiteral, - IsContainer = HasLocals | HasExports | HasMembers, + IsContainer = HasLocals | HasExports | HasMembers, PropertyOrAccessor = Property | Accessor, - Export = ExportNamespace | ExportType | ExportValue, + Export = ExportNamespace | ExportType | ExportValue, } export interface Symbol { @@ -1203,13 +1203,13 @@ module ts { } export const enum NodeCheckFlags { - TypeChecked = 0x00000001, // Node has been type checked - LexicalThis = 0x00000002, // Lexical 'this' reference - CaptureThis = 0x00000004, // Lexical 'this' used in body - EmitExtends = 0x00000008, // Emit __extends - SuperInstance = 0x00000010, // Instance 'super' reference - SuperStatic = 0x00000020, // Static 'super' reference - ContextChecked = 0x00000040, // Contextual types have been assigned + TypeChecked = 0x00000001, // Node has been type checked + LexicalThis = 0x00000002, // Lexical 'this' reference + CaptureThis = 0x00000004, // Lexical 'this' used in body + EmitExtends = 0x00000008, // Emit __extends + SuperInstance = 0x00000010, // Instance 'super' reference + SuperStatic = 0x00000020, // Static 'super' reference + ContextChecked = 0x00000040, // Contextual types have been assigned // Values for enum members have been computed, and any errors have been reported for them. EnumValuesComputed = 0x00000080, @@ -1228,26 +1228,26 @@ module ts { } export const enum TypeFlags { - Any = 0x00000001, - String = 0x00000002, - Number = 0x00000004, - Boolean = 0x00000008, - Void = 0x00000010, - Undefined = 0x00000020, - Null = 0x00000040, - Enum = 0x00000080, // Enum type - StringLiteral = 0x00000100, // String literal type - TypeParameter = 0x00000200, // Type parameter - Class = 0x00000400, // Class - Interface = 0x00000800, // Interface - Reference = 0x00001000, // Generic type reference - Tuple = 0x00002000, // Tuple - Union = 0x00004000, // Union - Anonymous = 0x00008000, // Anonymous - FromSignature = 0x00010000, // Created for signature assignment check - Unwidened = 0x00020000, // Unwidened type (is or contains Undefined or Null type) + Any = 0x00000001, + String = 0x00000002, + Number = 0x00000004, + Boolean = 0x00000008, + Void = 0x00000010, + Undefined = 0x00000020, + Null = 0x00000040, + Enum = 0x00000080, // Enum type + StringLiteral = 0x00000100, // String literal type + TypeParameter = 0x00000200, // Type parameter + Class = 0x00000400, // Class + Interface = 0x00000800, // Interface + Reference = 0x00001000, // Generic type reference + Tuple = 0x00002000, // Tuple + Union = 0x00004000, // Union + Anonymous = 0x00008000, // Anonymous + FromSignature = 0x00010000, // Created for signature assignment check + Unwidened = 0x00020000, // Unwidened type (is or contains Undefined or Null type) - Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null, + Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null, StringLike = String | StringLiteral, NumberLike = Number | Enum, ObjectType = Class | Interface | Reference | Tuple | Anonymous, @@ -1365,7 +1365,7 @@ module ts { inferences: TypeInferences[]; // Inferences made for each type parameter inferredTypes: Type[]; // Inferred type for each type parameter failedTypeParameterIndex?: number; // Index of type parameter for which inference failed - // It is optional because in contextual signature instantiation, nothing fails + // It is optional because in contextual signature instantiation, nothing fails } export interface DiagnosticMessage { @@ -1498,7 +1498,7 @@ module ts { narrowNoBreakSpace = 0x202F, ideographicSpace = 0x3000, mathematicalSpace = 0x205F, - ogham = 0x1680, + ogham = 0x1680, _ = 0x5F, $ = 0x24, @@ -1604,7 +1604,7 @@ module ts { tab = 0x09, // \t verticalTab = 0x0B, // \v } - + export interface CancellationToken { isCancellationRequested(): boolean; } @@ -1641,4 +1641,161 @@ module ts { intersectsWithPosition(position: number): boolean; intersection(span: TextSpan): TextSpan; } -} + + /** + * Creates a TextSpan instance beginning with the position Start and having the Length + * specified with length. + */ + /* + export function createTextSpan(start: number, length: number): TextSpan { + Debug.assert(start >= 0, "start"); + Debug.assert(length >= 0, "length"); + + function end() { + return start + length; + } + + function overlapsWith(span: TextSpan): boolean { + var overlapStart = Math.max(start, span.start()); + var overlapEnd = Math.min(end(), span.end()); + + return overlapStart < overlapEnd; + } + + function overlap(span: TextSpan): TextSpan { + var overlapStart = Math.max(start, span.start()); + var overlapEnd = Math.min(end(), span.end()); + + if (overlapStart < overlapEnd) { + return createTextSpanFromBounds(overlapStart, overlapEnd); + } + + return undefined; + } + + function intersectsWithTextSpan(span: TextSpan): boolean { + return span.start() <= end() && span.end() >= start; + } + + function intersectsWith(_start: number, _length: number): boolean { + var _end = _start + _length; + return _start <= end() && _end >= start; + } + + function intersectsWithPosition(position: number): boolean { + return position <= end() && position >= start; + } + + function intersection(span: TextSpan): TextSpan { + var intersectStart = Math.max(start, span.start()); + var intersectEnd = Math.min(end(), span.end()); + + if (intersectStart <= intersectEnd) { + return createTextSpanFromBounds(intersectStart, intersectEnd); + } + + return undefined; + } + + + return { + start: () => start, length: () => length, + toJSON: key => { start, length }, + end: end, + isEmpty: () => length === 0, + containsPosition: position => position >= start && position < end(), + containsTextSpan: span => span.start() >= start && span.end() <= end(), + overlapsWith: overlapsWith, + overlap: overlap, + intersectsWithTextSpan: intersectsWithTextSpan, + intersectsWith: intersectsWith, + intersectsWithPosition: intersectsWithPosition, + intersection: intersection, + } + } + */ + + export function createTextSpanFromBounds(start: number, end: number) { + return createTextSpan(start, end - start); + } + + export function createTextSpan(start: number, length: number): TextSpan { + return new (textSpanConstructor)(start, length); + } + + var textSpanConstructor = (function () { + function TextSpanObject(start: number, length: number) { + ts.Debug.assert(start >= 0, "start"); + ts.Debug.assert(length >= 0, "length"); + this._start = start; + this._length = length; + } + + TextSpanObject.prototype.toJSON = function (key: string) { + return { start: this._start, length: this._length }; + }; + + TextSpanObject.prototype.start = function () { + return this._start; + }; + + TextSpanObject.prototype.length = function () { + return this._length; + }; + + TextSpanObject.prototype.end = function () { + return this._start + this._length; + }; + + TextSpanObject.prototype.isEmpty = function () { + return this._length === 0; + }; + + TextSpanObject.prototype.containsPosition = function (position: number) { + return position >= this._start && position < this.end(); + }; + + TextSpanObject.prototype.containsTextSpan = function (span: TextSpan) { + return span.start() >= this._start && span.end() <= this.end(); + }; + + TextSpanObject.prototype.overlapsWith = function (span: TextSpan) { + var overlapStart = Math.max(this._start, span.start()); + var overlapEnd = Math.min(this.end(), span.end()); + return overlapStart < overlapEnd; + }; + + TextSpanObject.prototype.overlap = function (span: TextSpan) { + var overlapStart = Math.max(this._start, span.start()); + var overlapEnd = Math.min(this.end(), span.end()); + if (overlapStart < overlapEnd) { + return createTextSpanFromBounds(overlapStart, overlapEnd); + } + return undefined; + }; + + TextSpanObject.prototype.intersectsWithTextSpan = function (span: TextSpan) { + return span.start() <= this.end() && span.end() >= this._start; + }; + + TextSpanObject.prototype.intersectsWith = function (start: number, length: number) { + var end = start + length; + return start <= this.end() && end >= this._start; + }; + + TextSpanObject.prototype.intersectsWithPosition = function (position: number) { + return position <= this.end() && position >= this._start; + }; + + TextSpanObject.prototype.intersection = function (span: TextSpan) { + var intersectStart = Math.max(this._start, span.start()); + var intersectEnd = Math.min(this.end(), span.end()); + if (intersectStart <= intersectEnd) { + return createTextSpanFromBounds(intersectStart, intersectEnd); + } + return undefined; + }; + + return TextSpanObject; + })(); +} \ No newline at end of file diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index c88364de8b8..29a25a9923e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1755,14 +1755,14 @@ module FourSlash { public verifySemanticClassifications(expected: { classificationType: string; text: string }[]) { var actual = this.languageService.getSemanticClassifications(this.activeFile.fileName, - new ts.TextSpanObject(0, this.activeFile.content.length)); + ts.createTextSpan(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); } public verifySyntacticClassifications(expected: { classificationType: string; text: string }[]) { var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName, - new ts.TextSpanObject(0, this.activeFile.content.length)); + ts.createTextSpan(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); } @@ -1796,7 +1796,7 @@ module FourSlash { for (var i = 0; i < spans.length; i++) { var expectedSpan = spans[i]; var actualComment = actual[i]; - var actualCommentSpan = new ts.TextSpanObject(actualComment.position, actualComment.message.length); + var actualCommentSpan = ts.createTextSpan(actualComment.position, actualComment.message.length); if (expectedSpan.start !== actualCommentSpan.start() || expectedSpan.end !== actualCommentSpan.end()) { this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start() + ',' + actualCommentSpan.end() + ')'); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index c4a75772529..deefb2a4052 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -33,7 +33,7 @@ module Harness.LanguageService { this.editRanges.push({ length: this.content.length, textChangeRange: new ts.TextChangeRangeObject( - ts.TextSpanObject.fromBounds(minChar, limChar), newText.length) + ts.createTextSpanFromBounds(minChar, limChar), newText.length) }); // Update version # diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 4e4ad585503..e96a1974b2a 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -38,7 +38,7 @@ module ts.BreakpointResolver { return spanInNode(tokenAtLocation); function textSpan(startNode: Node, endNode?: Node) { - return TextSpanObject.fromBounds(startNode.getStart(), (endNode || startNode).getEnd()); + return createTextSpanFromBounds(startNode.getStart(), (endNode || startNode).getEnd()); } function spanInNodeIfStartsOnSameLine(node: Node, otherwiseOnNode?: Node): TextSpan { diff --git a/src/services/formatting.ts b/src/services/formatting.ts index 0cba03664d4..55e2a5832c4 100644 --- a/src/services/formatting.ts +++ b/src/services/formatting.ts @@ -868,7 +868,7 @@ module ts.formatting { } function newTextChange(start: number, len: number, newText: string): TextChange { - return { span: new TextSpanObject(start, len), newText } + return { span: createTextSpan(start, len), newText } } function recordDelete(start: number, len: number) { diff --git a/src/services/formatting/tokenSpan.ts b/src/services/formatting/tokenSpan.ts index dd328931919..926035094b1 100644 --- a/src/services/formatting/tokenSpan.ts +++ b/src/services/formatting/tokenSpan.ts @@ -16,9 +16,4 @@ /// module ts.formatting { - export class TokenSpan extends TextSpanObject { - constructor(public kind: SyntaxKind, start: number, length: number) { - super(start, length); - } - } } \ No newline at end of file diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index cfa3c98beb7..e7a500e2e9a 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -462,8 +462,8 @@ module ts.NavigationBar { function getNodeSpan(node: Node) { return node.kind === SyntaxKind.SourceFile - ? TextSpanObject.fromBounds(node.getFullStart(), node.getEnd()) - : TextSpanObject.fromBounds(node.getStart(), node.getEnd()); + ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) + : createTextSpanFromBounds(node.getStart(), node.getEnd()); } function getTextOfNode(node: Node): string { diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 9cbd2a69a38..5f44a2633a3 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -38,8 +38,8 @@ module ts { function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean) { if (hintSpanNode && startElement && endElement) { var span: OutliningSpan = { - textSpan: TextSpanObject.fromBounds(startElement.pos, endElement.end), - hintSpan: TextSpanObject.fromBounds(hintSpanNode.getStart(), hintSpanNode.end), + textSpan: createTextSpanFromBounds(startElement.pos, endElement.end), + hintSpan: createTextSpanFromBounds(hintSpanNode.getStart(), hintSpanNode.end), bannerText: collapseText, autoCollapse: autoCollapse }; @@ -88,7 +88,7 @@ module ts { else { // Block was a standalone block. In this case we want to only collapse // the span of the block, independent of any parent span. - var span = TextSpanObject.fromBounds(n.getStart(), n.end); + var span = createTextSpanFromBounds(n.getStart(), n.end); elements.push({ textSpan: span, hintSpan: span, diff --git a/src/services/services.ts b/src/services/services.ts index 93545fdaf8d..569754ffaba 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3219,7 +3219,7 @@ module ts { return { kind: ScriptElementKind.unknown, kindModifiers: ScriptElementKindModifier.none, - textSpan: new TextSpanObject(node.getStart(), node.getWidth()), + textSpan: createTextSpan(node.getStart(), node.getWidth()), displayParts: typeToDisplayParts(typeInfoResolver, type, getContainerNode(node)), documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined }; @@ -3233,7 +3233,7 @@ module ts { return { kind: displayPartsDocumentationsAndKind.symbolKind, kindModifiers: getSymbolModifiers(symbol), - textSpan: new TextSpanObject(node.getStart(), node.getWidth()), + textSpan: createTextSpan(node.getStart(), node.getWidth()), displayParts: displayPartsDocumentationsAndKind.displayParts, documentation: displayPartsDocumentationsAndKind.documentation }; @@ -3244,7 +3244,7 @@ module ts { function getDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo { return { fileName: node.getSourceFile().filename, - textSpan: TextSpanObject.fromBounds(node.getStart(), node.getEnd()), + textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd()), kind: symbolKind, name: symbolName, containerKind: undefined, @@ -3321,7 +3321,7 @@ module ts { if (referenceFile) { return [{ fileName: referenceFile.filename, - textSpan: TextSpanObject.fromBounds(0, 0), + textSpan: createTextSpanFromBounds(0, 0), kind: ScriptElementKind.scriptElement, name: comment.filename, containerName: undefined, @@ -3512,7 +3512,7 @@ module ts { if (shouldHighlightNextKeyword) { result.push({ fileName: filename, - textSpan: TextSpanObject.fromBounds(elseKeyword.getStart(), ifKeyword.end), + textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end), isWriteAccess: false }); i++; // skip the next keyword @@ -4204,7 +4204,7 @@ module ts { (findInComments && isInComment(position))) { result.push({ fileName: sourceFile.filename, - textSpan: new TextSpanObject(position, searchText.length), + textSpan: createTextSpan(position, searchText.length), isWriteAccess: false }); } @@ -4587,7 +4587,7 @@ module ts { return { fileName: node.getSourceFile().filename, - textSpan: TextSpanObject.fromBounds(start, end), + textSpan: createTextSpanFromBounds(start, end), isWriteAccess: isWriteAccess(node) }; } @@ -4643,7 +4643,7 @@ module ts { kindModifiers: getNodeModifiers(declaration), matchKind: MatchKind[matchKind], fileName: filename, - textSpan: TextSpanObject.fromBounds(declaration.getStart(), declaration.getEnd()), + textSpan: createTextSpanFromBounds(declaration.getStart(), declaration.getEnd()), // TODO(jfreeman): What should be the containerName when the container has a computed name? containerName: container && container.name ? (container.name).text : "", containerKind: container && container.name ? getNodeKind(container) : "" @@ -4920,7 +4920,7 @@ module ts { } } - return TextSpanObject.fromBounds(nodeForStartPos.getStart(), node.getEnd()); + return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd()); } function getBreakpointStatementAtPosition(filename: string, position: number) { @@ -4997,7 +4997,7 @@ module ts { var type = classifySymbol(symbol, getMeaningFromLocation(node)); if (type) { result.push({ - textSpan: new TextSpanObject(node.getStart(), node.getWidth()), + textSpan: createTextSpan(node.getStart(), node.getWidth()), classificationType: type }); } @@ -5023,7 +5023,7 @@ module ts { var width = comment.end - comment.pos; if (span.intersectsWith(comment.pos, width)) { result.push({ - textSpan: new TextSpanObject(comment.pos, width), + textSpan: createTextSpan(comment.pos, width), classificationType: ClassificationTypeNames.comment }); } @@ -5036,7 +5036,7 @@ module ts { var type = classifyTokenType(token); if (type) { result.push({ - textSpan: new TextSpanObject(token.getStart(), token.getWidth()), + textSpan: createTextSpan(token.getStart(), token.getWidth()), classificationType: type }); } @@ -5164,8 +5164,8 @@ module ts { var current = childNodes[i]; if (current.kind === matchKind) { - var range1 = new TextSpanObject(token.getStart(sourceFile), token.getWidth(sourceFile)); - var range2 = new TextSpanObject(current.getStart(sourceFile), current.getWidth(sourceFile)); + var range1 = createTextSpan(token.getStart(sourceFile), token.getWidth(sourceFile)); + var range2 = createTextSpan(current.getStart(sourceFile), current.getWidth(sourceFile)); // We want to order the braces when we return the result. if (range1.start() < range2.start()) { @@ -5416,7 +5416,7 @@ module ts { if (kind) { return getRenameInfo(symbol.name, typeInfoResolver.getFullyQualifiedName(symbol), kind, getSymbolModifiers(symbol), - new TextSpanObject(node.getStart(), node.getWidth())); + createTextSpan(node.getStart(), node.getWidth())); } } } diff --git a/src/services/shims.ts b/src/services/shims.ts index 1bafbafa005..c07ef320fdb 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -329,7 +329,7 @@ module ts { var decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded); return new TextChangeRangeObject( - new TextSpanObject(decoded.span.start, decoded.span.length), decoded.newLength); + createTextSpan(decoded.span.start, decoded.span.length), decoded.newLength); } } @@ -510,7 +510,7 @@ module ts { return this.forwardJSONCall( "getSyntacticClassifications('" + fileName + "', " + start + ", " + length + ")", () => { - var classifications = this.languageService.getSyntacticClassifications(fileName, new TextSpanObject(start, length)); + var classifications = this.languageService.getSyntacticClassifications(fileName, createTextSpan(start, length)); return classifications; }); } @@ -519,7 +519,7 @@ module ts { return this.forwardJSONCall( "getSemanticClassifications('" + fileName + "', " + start + ", " + length + ")", () => { - var classifications = this.languageService.getSemanticClassifications(fileName, new TextSpanObject(start, length)); + var classifications = this.languageService.getSemanticClassifications(fileName, createTextSpan(start, length)); return classifications; }); } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 9291bd0d685..426bd732882 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -367,7 +367,7 @@ module ts.SignatureHelp { // but not including parentheses) var applicableSpanStart = argumentsList.getFullStart(); var applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); - return new TextSpanObject(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression): TextSpan { @@ -391,7 +391,7 @@ module ts.SignatureHelp { } } - return new TextSpanObject(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } function getContainingArgumentInfo(node: Node): ArgumentListInfo { diff --git a/src/services/text.ts b/src/services/text.ts index 85465ffdf46..05b460cda08 100644 --- a/src/services/text.ts +++ b/src/services/text.ts @@ -1,138 +1,6 @@ module ts { - export class TextSpanObject { - private _start: number; - private _length: number; - - /** - * Creates a TextSpan instance beginning with the position Start and having the Length - * specified with length. - */ - constructor(start: number, length: number) { - Debug.assert(start >= 0, "start"); - Debug.assert(length >= 0, "length"); - - this._start = start; - this._length = length; - } - - public toJSON(key: any): any { - return { start: this._start, length: this._length }; - } - - public start(): number { - return this._start; - } - - public length(): number { - return this._length; - } - - public end(): number { - return this._start + this._length; - } - - public isEmpty(): boolean { - return this._length === 0; - } - - /** - * Determines whether the position lies within the span. Returns true if the position is greater than or equal to Start and strictly less - * than End, otherwise false. - * @param position The position to check. - */ - public containsPosition(position: number): boolean { - return position >= this._start && position < this.end(); - } - - /** - * Determines whether span falls completely within this span. Returns true if the specified span falls completely within this span, otherwise false. - * @param span The span to check. - */ - public containsTextSpan(span: TextSpan): boolean { - return span.start() >= this._start && span.end() <= this.end(); - } - - /** - * Determines whether the given span overlaps this span. Two spans are considered to overlap - * if they have positions in common and neither is empty. Empty spans do not overlap with any - * other span. Returns true if the spans overlap, false otherwise. - * @param span The span to check. - */ - public overlapsWith(span: TextSpan): boolean { - var overlapStart = Math.max(this._start, span.start()); - var overlapEnd = Math.min(this.end(), span.end()); - - return overlapStart < overlapEnd; - } - - /** - * Returns the overlap with the given span, or undefined if there is no overlap. - * @param span The span to check. - */ - public overlap(span: TextSpan): TextSpan { - var overlapStart = Math.max(this._start, span.start()); - var overlapEnd = Math.min(this.end(), span.end()); - - if (overlapStart < overlapEnd) { - return TextSpanObject.fromBounds(overlapStart, overlapEnd); - } - - return undefined; - } - - /** - * Determines whether span intersects this span. Two spans are considered to - * intersect if they have positions in common or the end of one span - * coincides with the start of the other span. Returns true if the spans intersect, false otherwise. - * @param The span to check. - */ - public intersectsWithTextSpan(span: TextSpan): boolean { - return span.start() <= this.end() && span.end() >= this._start; - } - - public intersectsWith(start: number, length: number): boolean { - var end = start + length; - return start <= this.end() && end >= this._start; - } - - /** - * Determines whether the given position intersects this span. - * A position is considered to intersect if it is between the start and - * end positions (inclusive) of this span. Returns true if the position intersects, false otherwise. - * @param position The position to check. - */ - public intersectsWithPosition(position: number): boolean { - return position <= this.end() && position >= this._start; - } - - /** - * Returns the intersection with the given span, or undefined if there is no intersection. - * @param span The span to check. - */ - public intersection(span: TextSpan): TextSpan { - var intersectStart = Math.max(this._start, span.start()); - var intersectEnd = Math.min(this.end(), span.end()); - - if (intersectStart <= intersectEnd) { - return TextSpanObject.fromBounds(intersectStart, intersectEnd); - } - - return undefined; - } - - /** - * Creates a new TextSpan from the given start and end positions - * as opposed to a position and length. - */ - public static fromBounds(start: number, end: number): TextSpan { - Debug.assert(start >= 0); - Debug.assert(end - start >= 0); - return new TextSpanObject(start, end - start); - } - } - export class TextChangeRangeObject implements TextChangeRange { - public static unchanged = new TextChangeRangeObject(new TextSpanObject(0, 0), 0); + public static unchanged = new TextChangeRangeObject(createTextSpan(0, 0), 0); private _span: TextSpan; private _newLength: number; @@ -162,7 +30,7 @@ module ts { } public newSpan(): TextSpan { - return new TextSpanObject(this.span().start(), this.newLength()); + return createTextSpan(this.span().start(), this.newLength()); } public isUnchanged(): boolean { @@ -290,7 +158,7 @@ module ts { newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)); } - return new TextChangeRangeObject(TextSpanObject.fromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); + return new TextChangeRangeObject(createTextSpanFromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); } } } \ No newline at end of file diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index 9bd9df5deca..4cd816a3a3a 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -6,7 +6,7 @@ module ts { var contents = text.getText(0, text.getLength()); var newContents = contents.substr(0, start) + newText + contents.substring(start + length); - return { text: ScriptSnapshot.fromString(newContents), textChangeRange: new TextChangeRangeObject(new TextSpanObject(start, length), newText.length) } + return { text: ScriptSnapshot.fromString(newContents), textChangeRange: new TextChangeRangeObject(createTextSpan(start, length), newText.length) } } function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { From 888b88ee43f476068b77269f33b60dca428c44eb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 14:36:37 -0800 Subject: [PATCH 05/50] Move textSpan and textChangeRange impls to the compiler layer. --- src/compiler/types.ts | 374 +++++++++++++-------- src/harness/harnessLanguageService.ts | 6 +- src/services/services.ts | 2 +- src/services/shims.ts | 2 +- src/services/text.ts | 162 --------- tests/cases/unittests/incrementalParser.ts | 2 +- 6 files changed, 232 insertions(+), 316 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 48538173e67..451f8ca1b85 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1620,13 +1620,6 @@ module ts { getNewLine(): string; } - export interface TextChangeRange { - span(): TextSpan; - newLength(): number; - newSpan(): TextSpan; - isUnchanged(): boolean; - } - export interface TextSpan { start(): number; length(): number; @@ -1642,160 +1635,245 @@ module ts { intersection(span: TextSpan): TextSpan; } - /** - * Creates a TextSpan instance beginning with the position Start and having the Length - * specified with length. - */ - /* + var textSpanConstructor = (function () { + function textSpanConstructor(start: number, length: number) { + if (start < 0) { + throw new Error("start < 0"); + } + if (length < 0) { + throw new Error("start < 0"); + } + this._start = start; + this._length = length; + } + + textSpanConstructor.prototype = { + toJSON(key: string) { + return { start: this._start, length: this._length } + }, + start() { + return this._start + }, + length() { + return this._length + }, + end() { + return this._start + this._length + }, + isEmpty() { + return this._length === 0 + }, + containsPosition(position: number) { + return position >= this._start && position < this.end() + }, + containsTextSpan(span: TextSpan) { + return span.start() >= this._start && span.end() <= this.end() + }, + overlapsWith(span: TextSpan) { + var overlapStart = Math.max(this._start, span.start()); + var overlapEnd = Math.min(this.end(), span.end()); + return overlapStart < overlapEnd; + }, + overlap(span: TextSpan) { + var overlapStart = Math.max(this._start, span.start()); + var overlapEnd = Math.min(this.end(), span.end()); + if (overlapStart < overlapEnd) { + return createTextSpanFromBounds(overlapStart, overlapEnd); + } + return undefined; + }, + intersectsWithTextSpan(span: TextSpan) { + return span.start() <= this.end() && span.end() >= this._start + }, + intersectsWith(start: number, length: number) { + var end = start + length; + return start <= this.end() && end >= this._start; + }, + intersectsWithPosition(position: number) { + return position <= this.end() && position >= this._start; + }, + intersection(span: TextSpan) { + var intersectStart = Math.max(this._start, span.start()); + var intersectEnd = Math.min(this.end(), span.end()); + if (intersectStart <= intersectEnd) { + return createTextSpanFromBounds(intersectStart, intersectEnd); + } + return undefined; + } + }; + + return textSpanConstructor; + })(); + export function createTextSpan(start: number, length: number): TextSpan { - Debug.assert(start >= 0, "start"); - Debug.assert(length >= 0, "length"); - - function end() { - return start + length; - } - - function overlapsWith(span: TextSpan): boolean { - var overlapStart = Math.max(start, span.start()); - var overlapEnd = Math.min(end(), span.end()); - - return overlapStart < overlapEnd; - } - - function overlap(span: TextSpan): TextSpan { - var overlapStart = Math.max(start, span.start()); - var overlapEnd = Math.min(end(), span.end()); - - if (overlapStart < overlapEnd) { - return createTextSpanFromBounds(overlapStart, overlapEnd); - } - - return undefined; - } - - function intersectsWithTextSpan(span: TextSpan): boolean { - return span.start() <= end() && span.end() >= start; - } - - function intersectsWith(_start: number, _length: number): boolean { - var _end = _start + _length; - return _start <= end() && _end >= start; - } - - function intersectsWithPosition(position: number): boolean { - return position <= end() && position >= start; - } - - function intersection(span: TextSpan): TextSpan { - var intersectStart = Math.max(start, span.start()); - var intersectEnd = Math.min(end(), span.end()); - - if (intersectStart <= intersectEnd) { - return createTextSpanFromBounds(intersectStart, intersectEnd); - } - - return undefined; - } - - - return { - start: () => start, length: () => length, - toJSON: key => { start, length }, - end: end, - isEmpty: () => length === 0, - containsPosition: position => position >= start && position < end(), - containsTextSpan: span => span.start() >= start && span.end() <= end(), - overlapsWith: overlapsWith, - overlap: overlap, - intersectsWithTextSpan: intersectsWithTextSpan, - intersectsWith: intersectsWith, - intersectsWithPosition: intersectsWithPosition, - intersection: intersection, - } + return new (textSpanConstructor)(start, length); } - */ export function createTextSpanFromBounds(start: number, end: number) { return createTextSpan(start, end - start); } - export function createTextSpan(start: number, length: number): TextSpan { - return new (textSpanConstructor)(start, length); + export interface TextChangeRange { + span(): TextSpan; + newLength(): number; + newSpan(): TextSpan; + isUnchanged(): boolean; } - var textSpanConstructor = (function () { - function TextSpanObject(start: number, length: number) { - ts.Debug.assert(start >= 0, "start"); - ts.Debug.assert(length >= 0, "length"); - this._start = start; - this._length = length; + var textChangeRangeConstructor = (function () { + function textChangeRangeConstructor(span: TextSpan, newLength: number) { + if (newLength < 0) { + throw new Error("newLength < 0"); + } + this._span = span; + this._newLength = newLength; } - TextSpanObject.prototype.toJSON = function (key: string) { - return { start: this._start, length: this._length }; - }; - - TextSpanObject.prototype.start = function () { - return this._start; - }; - - TextSpanObject.prototype.length = function () { - return this._length; - }; - - TextSpanObject.prototype.end = function () { - return this._start + this._length; - }; - - TextSpanObject.prototype.isEmpty = function () { - return this._length === 0; - }; - - TextSpanObject.prototype.containsPosition = function (position: number) { - return position >= this._start && position < this.end(); - }; - - TextSpanObject.prototype.containsTextSpan = function (span: TextSpan) { - return span.start() >= this._start && span.end() <= this.end(); - }; - - TextSpanObject.prototype.overlapsWith = function (span: TextSpan) { - var overlapStart = Math.max(this._start, span.start()); - var overlapEnd = Math.min(this.end(), span.end()); - return overlapStart < overlapEnd; - }; - - TextSpanObject.prototype.overlap = function (span: TextSpan) { - var overlapStart = Math.max(this._start, span.start()); - var overlapEnd = Math.min(this.end(), span.end()); - if (overlapStart < overlapEnd) { - return createTextSpanFromBounds(overlapStart, overlapEnd); + textChangeRangeConstructor.prototype = { + span() { + return this._span; + }, + newLength() { + return this._newLength; + }, + newSpan() { + return createTextSpan(this.span().start(), this.newLength()); + }, + isUnchanged() { + return this.span().isEmpty() && this.newLength() === 0; } - return undefined; }; - TextSpanObject.prototype.intersectsWithTextSpan = function (span: TextSpan) { - return span.start() <= this.end() && span.end() >= this._start; - }; - - TextSpanObject.prototype.intersectsWith = function (start: number, length: number) { - var end = start + length; - return start <= this.end() && end >= this._start; - }; - - TextSpanObject.prototype.intersectsWithPosition = function (position: number) { - return position <= this.end() && position >= this._start; - }; - - TextSpanObject.prototype.intersection = function (span: TextSpan) { - var intersectStart = Math.max(this._start, span.start()); - var intersectEnd = Math.min(this.end(), span.end()); - if (intersectStart <= intersectEnd) { - return createTextSpanFromBounds(intersectStart, intersectEnd); - } - return undefined; - }; - - return TextSpanObject; + return textChangeRangeConstructor; })(); + + export function createTextChangeRange(span: TextSpan, newLength: number): TextChangeRange { + return new (textChangeRangeConstructor)(span, newLength); + } + + export var 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. + var change0 = changes[0]; + + var oldStartN = change0.span().start(); + var oldEndN = change0.span().end(); + var newEndN = oldStartN + change0.newLength(); + + for (var i = 1; i < changes.length; i++) { + var 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)) + // } + + var oldStart1 = oldStartN; + var oldEnd1 = oldEndN; + var newEnd1 = newEndN; + + var oldStart2 = nextChange.span().start(); + var oldEnd2 = nextChange.span().end(); + var 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); + } } \ No newline at end of file diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index deefb2a4052..c8186b6af7a 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -32,7 +32,7 @@ module Harness.LanguageService { // Store edit range + new length of script this.editRanges.push({ length: this.content.length, - textChangeRange: new ts.TextChangeRangeObject( + textChangeRange: ts.createTextChangeRange( ts.createTextSpanFromBounds(minChar, limChar), newText.length) }); @@ -43,14 +43,14 @@ module Harness.LanguageService { public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { if (startVersion === endVersion) { // No edits! - return ts.TextChangeRangeObject.unchanged; + return ts.unchangedTextChangeRange; } var initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); var lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); var entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); - return ts.TextChangeRangeObject.collapseChangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); + return ts.collapseTextChangeRangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); } } diff --git a/src/services/services.ts b/src/services/services.ts index 569754ffaba..868850085d8 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1613,7 +1613,7 @@ module ts { public getChangeRange(filename: string, lastKnownVersion: string, oldScriptSnapshot: IScriptSnapshot): TextChangeRange { var currentVersion = this.getVersion(filename); if (lastKnownVersion === currentVersion) { - return TextChangeRangeObject.unchanged; // "No changes" + return unchangedTextChangeRange; // "No changes" } var scriptSnapshot = this.getScriptSnapshot(filename); diff --git a/src/services/shims.ts b/src/services/shims.ts index c07ef320fdb..459a56518bd 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -328,7 +328,7 @@ module ts { } var decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded); - return new TextChangeRangeObject( + return createTextChangeRange( createTextSpan(decoded.span.start, decoded.span.length), decoded.newLength); } } diff --git a/src/services/text.ts b/src/services/text.ts index 05b460cda08..12cb1b9d4b7 100644 --- a/src/services/text.ts +++ b/src/services/text.ts @@ -1,164 +1,2 @@ module ts { - export class TextChangeRangeObject implements TextChangeRange { - public static unchanged = new TextChangeRangeObject(createTextSpan(0, 0), 0); - - private _span: TextSpan; - private _newLength: number; - - /** - * Initializes a new instance of TextChangeRange. - */ - constructor(span: TextSpan, newLength: number) { - Debug.assert(newLength >= 0, "newLength"); - - this._span = span; - this._newLength = newLength; - } - - /** - * The span of text before the edit which is being changed - */ - public span(): TextSpan { - return this._span; - } - - /** - * Width of the span after the edit. A 0 here would represent a delete - */ - public newLength(): number { - return this._newLength; - } - - public newSpan(): TextSpan { - return createTextSpan(this.span().start(), this.newLength()); - } - - public isUnchanged(): boolean { - return this.span().isEmpty() && this.newLength() === 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. - */ - public static collapseChangesAcrossMultipleVersions(changes: TextChangeRange[]): TextChangeRange { - if (changes.length === 0) { - return TextChangeRangeObject.unchanged; - } - - 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. - var change0 = changes[0]; - - var oldStartN = change0.span().start(); - var oldEndN = change0.span().end(); - var newEndN = oldStartN + change0.newLength(); - - for (var i = 1; i < changes.length; i++) { - var 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)) - // } - - var oldStart1 = oldStartN; - var oldEnd1 = oldEndN; - var newEnd1 = newEndN; - - var oldStart2 = nextChange.span().start(); - var oldEnd2 = nextChange.span().end(); - var newEnd2 = oldStart2 + nextChange.newLength(); - - oldStartN = Math.min(oldStart1, oldStart2); - oldEndN = Math.max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)); - newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)); - } - - return new TextChangeRangeObject(createTextSpanFromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); - } - } } \ No newline at end of file diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index 4cd816a3a3a..9e7e307b069 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -6,7 +6,7 @@ module ts { var contents = text.getText(0, text.getLength()); var newContents = contents.substr(0, start) + newText + contents.substring(start + length); - return { text: ScriptSnapshot.fromString(newContents), textChangeRange: new TextChangeRangeObject(createTextSpan(start, length), newText.length) } + return { text: ScriptSnapshot.fromString(newContents), textChangeRange: createTextChangeRange(createTextSpan(start, length), newText.length) } } function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { From f9f9b374d15b973e8cdabc33be6cec714c643c5e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 16:33:14 -0800 Subject: [PATCH 06/50] When updating the text for a source file, extrend the changed range. This ensures that nodes/tokens affected by lookahead will be reparsed. --- src/compiler/parser.ts | 130 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 57300aa475c..0bca454841f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1185,13 +1185,141 @@ module ts { return sourceFile; } - + // Make the actual change larger so that we know to reparse anything whose lookahead + // might have intersected the change. + var changeRange = extendToAffectedRange(textChangeRange); // Don't pass along the text change range for now. We'll pass it along once incremental // parsing is enabled. return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); } + function extendToAffectedRange(changeRange: TextChangeRange): TextChangeRange { + // Consider the following code: + // void foo() { /; } + // + // If the text changes with an insertion of / just before the semicolon then we end up with: + // void foo() { //; } + // + // If we were to just use the changeRange a is, then we would not rescan the { token + // (as it does not intersect the actual original change range). Because an edit may + // change the token touching it, we actually need to look back *at least* one token so + // that the prior token sees that change. + var maxLookahead = 1; + + var start = changeRange.span().start(); + + // the first iteration aligns us with the change start. subsequent iteration move us to + // the left by maxLookahead tokens. We only need to do this as long as we're not at the + // start of the tree. + for (var i = 0; start > 0 && i <= maxLookahead; i++) { + var nearestNode = findNearestNodeStartingBeforeOrAtPosition(start); + var position = nearestNode.pos; + + start = Math.max(0, position - 1); + } + + var finalSpan = createTextSpanFromBounds(start, changeRange.span().end()); + var finalLength = changeRange.newLength() + (changeRange.span().start() - start); + + return createTextChangeRange(finalSpan, finalLength); + } + + function findNearestNodeStartingBeforeOrAtPosition(position: number): Node { + var bestResult: Node = sourceFile; + var lastNodeEntirelyBeforePosition: Node; + + forEachChild(sourceFile, visit); + + if (lastNodeEntirelyBeforePosition) { + var lastChildOfLastEntireNodeBeforePosition = getLastChild(lastNodeEntirelyBeforePosition); + if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { + bestResult = lastChildOfLastEntireNodeBeforePosition; + } + } + + return bestResult; + + function getLastChild(node: Node): Node { + while (true) { + var lastChild = getLastChildWorker(node); + if (lastChild) { + node = lastChild; + } + else { + return node; + } + } + } + + function getLastChildWorker(node: Node): Node { + var last:Node = undefined; + forEachChild(node, child => { + if (!isMissingNode(child)) { + last = child; + } + }); + return last; + } + + function visit(child: Node) { + if (isMissingNode(child)) { + // Missing nodes are effectively invisible to us. We never even consider them + // When trying to find the nearest node before us. + return; + } + + // If the child intersects this position, then this node is currently the nearest + // node that starts before the position. + if (child.pos <= position) { + if (child.pos >= bestResult.pos) { + // This node starts before the position, and is closer to the position than + // the previous best node we found. It is now the new best node. + bestResult = child; + } + + // Now, the node may overlap the position, or it may end entirely before the + // position. If it overlaps with the position, then either it, or one of its + // children must be the nearest node before the position. So we can just + // recurse into this child to see if we can find something better. + if (position < child.end) { + // The nearest node is either this child, or one of the children inside + // of it. We've already marked this child as the best so far. Recurse + // in case one of the children is better. + forEachChild(child, visit); + + // Once we look at the children of this node, then there's no need to + // continue any further. + return true; + } + else { + Debug.assert(child.end <= position); + // The child ends entirely before this position. Say you have the following + // (where $ is the position) + // + // ? $ : <...> <...> + // + // We would want to find the nearest preceding node in "complex expr 2". + // To support that, we keep track of this node, and once we're done searching + // for a best node, we recurse down this node to see if we can find a good + // result in it. + // + // This approach allows us to quickly skip over nodes that are entirely + // before the position, while still allowing us to find any nodes in the + // last one that might be what we want. + lastNodeEntirelyBeforePosition = child; + } + } + else { + Debug.assert(child.pos > position); + // We're now at a node that is entirely past the position we're searching for. + // This node (and all following nodes) could never contribute to the result, + // so just skip them by returning 'true' here. + return true; + } + } + } + function setContextFlag(val: Boolean, flag: ParserContextFlags) { if (val) { contextFlags |= flag; From 784872678421ce3d09d9bb8e4d3e8e6394720d74 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 17:05:56 -0800 Subject: [PATCH 07/50] Sweep and mark nodes before going and performing incremental parsing. --- src/compiler/parser.ts | 109 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0bca454841f..79484dfd2d5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1037,6 +1037,19 @@ module ts { forEachChild(sourceFile, walk); } + interface IncrementalElement extends TextRange { + intersectsChange: boolean + length?: number; + _children: Node[]; + } + + interface IncrementalNode extends Node, IncrementalElement { + } + + interface IncrementalNodeArray extends NodeArray, IncrementalElement { + length: number + } + export function createSourceFile(filename: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false): SourceFile { var parsingContext: ParsingContext; var identifiers: Map; @@ -1189,11 +1202,107 @@ module ts { // might have intersected the change. var changeRange = extendToAffectedRange(textChangeRange); + // The is the amount the nodes after the edit range need to be adjusted. It can be + // positive (if the edit added characters), negative (if the edit deleted characters) + // or zero (if this was a pure overwrite with nothing added/removed). + var delta = changeRange.newSpan().length() - changeRange.span().length(); + + // If we added or removed characters during the edit, then we need to go and adjust all + // the nodes after the edit. Those nodes may move forward down (if we inserted chars) + // or they may move backward (if we deleted chars). + // + // Doing this helps us out in two ways. First, it means that any nodes/tokens we want + // to reuse are already at the appropriate position in the new text. That way when we + // reuse them, we don't have to figure out if they need to be adjusted. Second, it makes + // it very easy to determine if we can reuse a node. If the node's position is at where + // we are in the text, then we can reuse it. Otherwise we can't. If hte node's position + // is ahead of us, then we'll need to rescan tokens. If the node's position is behind + // us, then we'll need to skip it or crumble it as appropriate + // + // Also, mark any syntax elements that intersect the changed span. We know, up front, + // that we cannot reuse these elements. + updateTokenPositionsAndMarkElements(sourceFile, + changeRange.span().start(), changeRange.span().end(), delta); + // Don't pass along the text change range for now. We'll pass it along once incremental // parsing is enabled. return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); } + function updateTokenPositionsAndMarkElements(node: IncrementalNode, changeStart: number, changeRangeOldEnd: number, delta: number): void { + forEachChild(node, visitNode, visitArray); + + function visitNode(child: IncrementalNode) { + if (child.pos > changeRangeOldEnd) { + forceUpdateTokenPositionsForElement(child, delta); + } + else { + // Check if the element intersects the change range. If it does, then it is not + // reusable. Also, we'll need to recurse to see what constituent portions we may + // be able to use. + var fullEnd = child.end; + if (fullEnd >= changeStart) { + child.intersectsChange = true; + forEachChild(child, visitNode, visitArray); + } + // else { + // Otherwise, the node is entirely before the change range. No need to do anything with it. + // } + } + } + + function visitArray(array: IncrementalNodeArray) { + if (array.pos > changeRangeOldEnd) { + // Array is entirely after the change range. We need to move it, and move any of + // its children. + forceUpdateTokenPositionsForElement(array, delta); + } + else { + // Check if the element intersects the change range. If it does, then it is not + // reusable. Also, we'll need to recurse to see what constituent portions we may + // be able to use. + var fullEnd = array.end; + if (fullEnd >= changeStart) { + array.intersectsChange = true; + for (var i = 0, n = array.length; i < n; i++) { + forEachChild(array[i], visitNode, visitArray); + } + } + // else { + // Otherwise, the array is entirely before the change range. No need to do anything with it. + // } + } + } + } + + function forceUpdateTokenPositionsForElement(element: IncrementalElement, delta: number) { + if (element.length) { + visitArray(element); + } + else { + visitNode(element); + } + + function visitNode(node: IncrementalNode) { + // Ditch any existing LS children we may have created. This way we can avoid + // moving them forward. + node._children = undefined; + node.pos += delta; + node.end += delta; + + forEachChild(node, visitNode, visitArray); + } + + function visitArray(array: IncrementalNodeArray) { + array.pos += delta; + array.end += delta; + + for (var i = 0, n = array.length; i < n; i++) { + forEachChild(array[i], visitNode, visitArray); + } + } + } + function extendToAffectedRange(changeRange: TextChangeRange): TextChangeRange { // Consider the following code: // void foo() { /; } From 28b7ed93183f5262868dde36adb6bc32780c056c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 17:47:51 -0800 Subject: [PATCH 08/50] Initial stubs for the incremental parser logic. --- src/compiler/parser.ts | 272 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 268 insertions(+), 4 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 79484dfd2d5..76ec1dfa183 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1845,8 +1845,13 @@ module ts { } // True if positioned at the start of a list element - function isListElement(kind: ParsingContext, inErrorRecovery: boolean): boolean { - switch (kind) { + function isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean { + var node = currentNode(parsingContext); + if (node) { + return true; + } + + switch (parsingContext) { case ParsingContext.SourceElements: case ParsingContext.ModuleElements: return isSourceElement(inErrorRecovery); @@ -2001,7 +2006,7 @@ module ts { while (!isListTerminator(kind)) { if (isListElement(kind, /* inErrorRecovery */ false)) { - var element = parseElement(); + var element = currentNode(kind) || parseElement(); result.push(element); // test elements only if we are not already in strict mode @@ -2031,6 +2036,265 @@ module ts { return result; } + function currentNode(parsingContext: ParsingContext): Node { + var node: Node = currentNodeFromCursor(); + if (!node) { + return undefined; + } + + // We can only reuse a node if it was parsed under the same strict mode that we're + // currently in. i.e. if we originally parsed a node in non-strict mode, but then + // the user added 'using strict' at the top of the file, then we can't use that node + // again as the presense of strict mode may cause us to parse the tokens in the file + // differetly. + // + // Note: we *can* reuse tokens when the strict mode changes. That's because tokens + // are unaffected by strict mode. It's just the parser will decide what to do with it + // differently depending on what mode it is in. + // + // This also applies to all our other context flags as well. + if (node.parserContextFlags !== contextFlags) { + return undefined; + } + + // Ok, we have a node that looks like it could be reused. Now verify that it is valid + // in the currest list parsing context that we're currently at. + if (!canReuseNode(node, parsingContext)) { + return undefined; + } + + // It was valid. Let teh source know we're consuming this node, and pass to the list + // parser. + return consumeNode(node); + } + + function consumeNode(node: Node) { + // Move the scanner so it is after the node we just consumed + scanner.setTextPos(node.end); + nextToken(); + return node; + } + + function canReuseNode(node: Node, parsingContext: ParsingContext): boolean { + switch (parsingContext) { + case ParsingContext.ModuleElements: + return isReusableModuleElement(node); + + case ParsingContext.ClassMembers: + return isReusableClassMember(node); + + case ParsingContext.SwitchClauses: + return isReusableSwitchClause(node); + + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauseStatements: + return isReusableStatement(node); + + case ParsingContext.EnumMembers: + return isReusableEnumMember(node); + + case ParsingContext.TypeMembers: + return isReusableTypeMember(node); + + case ParsingContext.VariableDeclarations: + return isReusableVariableDeclaration(node); + + case ParsingContext.Parameters: + return isReusableParameter(node); + + // Any other lists we do not care about reusing nodes in. But feel free to add if + // you can do so safely. Danger areas involve nodes that may involve speculative + // parsing. If speculative parsing is involved with the node, then the range the + // parser reached while looking ahead might be in the edited range (see the example + // in canReuseVariableDeclaratorNode for a good case of this). + case ParsingContext.HeritageClauses: + // This would probably be safe to reuse. There is no speculative parsing with + // heritage clauses. + + case ParsingContext.TypeReferences: + // This would probably be safe to reuse. There is no speculative parsing with + // type names in a heritage clause. There can be generic names in the type + // name list. But because it is a type context, we never use speculative + // parsing on the type argument list. + + case ParsingContext.TypeParameters: + // This would probably be safe to reuse. There is no speculative parsing with + // type parameters. Note that that's because type *parameters* only occur in + // unambiguous *type* contexts. While type *arguments* occur in very ambiguous + // *expression* contexts. + + case ParsingContext.TupleElementTypes: + // This would probably be safe to reuse. There is no speculative parsing with + // tuple types. + + // Technically, type argument list types are probably safe to reuse. While + // speculative parsing is involved with them (since type argument lists are only + // produced from speculative parsing a < as a type argument list), we only have + // the types because speculative parsing succeeded. Thus, the lookahead never + // went past the end of the list and rewound. + case ParsingContext.TypeArguments: + + // Note: these are almost certainly not safe to ever reuse. Expressions commonly + // need a large amount of lookahead, and we should not reuse them as they may + // have actually intersected the edit. + case ParsingContext.ArgumentExpressions: + + // This is not safe to reuse for the same reason as the 'AssignmentExpression' + // cases. i.e. a property assignment may end with an expression, and thus might + // have lookahead far beyond it's old node. + case ParsingContext.ObjectLiteralMembers: + } + + return false; + } + + function isReusableModuleElement(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportAssignment: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + + // Keep in sync with isStatement: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.Block: + case SyntaxKind.IfStatement: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.ThrowStatement: + case SyntaxKind.ReturnStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.EmptyStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.LabeledStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.DebuggerStatement: + return true; + } + } + + return false; + } + + function isReusableClassMember(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.IndexSignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + return true; + } + } + + return false; + } + + function isReusableSwitchClause(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + return true; + } + } + + return false; + } + + function isReusableStatement(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.Block: + case SyntaxKind.IfStatement: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.ThrowStatement: + case SyntaxKind.ReturnStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.EmptyStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.LabeledStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.DebuggerStatement: + return true; + } + } + + return false; + } + + function isReusableEnumMember(node: Node) { + return node.kind === SyntaxKind.EnumMember; + } + + function isReusableTypeMember(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.ConstructSignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.PropertySignature: + case SyntaxKind.CallSignature: + return true; + } + } + + return false; + } + + function isReusableVariableDeclaration(node: Node) { + if (node.kind !== SyntaxKind.VariableDeclaration) { + return false; + } + + // Very subtle incremental parsing bug. Consider the following code: + // + // var v = new List < A, B + // + // This is actually legal code. It's a list of variable declarators "v = new List() + // + // then we have a problem. "v = new Listnode; + return variableDeclarator.initializer === undefined; + } + + function isReusableParameter(node: Node) { + // TODO: this most likely needs the same initializer check that + // isReusableVariableDeclaration has. + return node.kind === SyntaxKind.Parameter; + } + + function currentNodeFromCursor(): Node { + // NYI + return undefined; + } + // Returns true if we should abort parsing. function abortParsingListOrMoveToNextToken(kind: ParsingContext) { parseErrorAtCurrentToken(parsingContextErrors(kind)); @@ -2052,7 +2316,7 @@ module ts { var commaStart = -1; // Meaning the previous token was not a comma while (true) { if (isListElement(kind, /* inErrorRecovery */ false)) { - result.push(parseElement()); + result.push(currentNode(kind) || parseElement()); commaStart = scanner.getTokenPos(); if (parseOptional(SyntaxKind.CommaToken)) { continue; From 3699a4079f8574de7c0947dc5f521f357bd80d6e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 17:52:42 -0800 Subject: [PATCH 09/50] Rename method. --- src/compiler/parser.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d810c5126fa..3e8301ef7f7 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1585,7 +1585,7 @@ module ts { return isSourceElement(inErrorRecovery); case ParsingContext.BlockStatements: case ParsingContext.SwitchClauseStatements: - return isStatement(inErrorRecovery); + return isStartOfStatement(inErrorRecovery); case ParsingContext.SwitchClauses: return token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword; case ParsingContext.TypeMembers: @@ -2886,7 +2886,7 @@ module ts { return parseFunctionBlock(/*allowYield:*/ false, /* ignoreMissingOpenBrace */ false); } - if (isStatement(/* inErrorRecovery */ true) && !isStartOfExpressionStatement() && token !== SyntaxKind.FunctionKeyword) { + if (isStartOfStatement(/*inErrorRecovery:*/ true) && !isStartOfExpressionStatement() && token !== SyntaxKind.FunctionKeyword) { // Check if we got a plain statement (i.e. no expression-statements, no functions expressions/declarations) // // Here we try to recover from a potential error situation in the case where the @@ -3748,7 +3748,7 @@ module ts { return finishNode(node); } - function isStatement(inErrorRecovery: boolean): boolean { + function isStartOfStatement(inErrorRecovery: boolean): boolean { switch (token) { case SyntaxKind.SemicolonToken: // If we're in error recovery, then we don't want to treat ';' as an empty statement. @@ -4455,7 +4455,7 @@ module ts { } function isSourceElement(inErrorRecovery: boolean): boolean { - return isDeclarationStart() || isStatement(inErrorRecovery); + return isDeclarationStart() || isStartOfStatement(inErrorRecovery); } function parseSourceElement() { From fc27f72324f7bd38c797df528df25f382283be20 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 18:52:56 -0800 Subject: [PATCH 10/50] Understand and handle modifiers on function declarations and variable statements within blocks. This ensures reusability for functions/variables that may have been outside a block, but end up inside one afterwards. It also ensure the same tree is produced when incremental parsing. i.e. if you have: declare function F() { } And you add a { above it, then we current have an incremental parsing bug. Namely we would see a FunctionDeclaration node and say 'yes, we can reuse that node while parsing the block'. This is currently broken because the normal parse would not have normally accepted such a node (because of the modifiers). This was an example of contextual parsing of the same kind of node. Something which we do not want to do if we want incremental parsing to work properly. --- .../diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 4 ++ src/compiler/parser.ts | 51 ++++++++++++++++++- ...rserModifierOnStatementInBlock2.errors.txt | 9 ++-- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 17dbae83bf2..4520aaffc02 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -144,6 +144,7 @@ module ts { Array_element_destructuring_pattern_expected: { code: 1181, category: DiagnosticCategory.Error, key: "Array element destructuring pattern expected." }, A_destructuring_declaration_must_have_an_initializer: { code: 1182, category: DiagnosticCategory.Error, key: "A destructuring declaration must have an initializer." }, Destructuring_declarations_are_not_allowed_in_ambient_contexts: { code: 1183, category: DiagnosticCategory.Error, key: "Destructuring declarations are not allowed in ambient contexts." }, + Modifiers_cannot_appear_here: { code: 1184, category: DiagnosticCategory.Error, key: "Modifiers cannot appear here." }, Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." }, Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor: { code: 2301, category: DiagnosticCategory.Error, key: "Initializer of instance member variable '{0}' cannot reference identifier '{1}' declared in the constructor." }, Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a06b71d460e..7aacd21cea4 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -568,6 +568,10 @@ "category": "Error", "code": 1183 }, + "Modifiers cannot appear here.": { + "category": "Error", + "code": 1184 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 3e8301ef7f7..9552e0f9cc9 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3866,12 +3866,45 @@ module ts { } // Else parse it like identifier - fall through default: + if (isModifier(token)) { + var result = tryParse(parseVariableStatementOrFunctionDeclarationWithModifiers); + if (result) { + return result; + } + } + return isLabel() ? parseLabeledStatement() : parseExpressionStatement(); } } + function parseVariableStatementOrFunctionDeclarationWithModifiers(): FunctionDeclaration | VariableStatement { + var start = scanner.getStartPos(); + var modifiers = parseModifiers(); + switch (token) { + case SyntaxKind.ConstKeyword: + var nextTokenIsEnum = lookAhead(nextTokenIsEnumKeyword) + if (nextTokenIsEnum) { + return undefined; + } + return parseVariableStatement(start, modifiers); + + case SyntaxKind.LetKeyword: + if (!isLetDeclaration()) { + return undefined; + } + return parseVariableStatement(start, modifiers); + + case SyntaxKind.VarKeyword: + return parseVariableStatement(start, modifiers); + case SyntaxKind.FunctionKeyword: + return parseFunctionDeclaration(start, modifiers); + } + + return undefined; + } + function parseFunctionBlockOrSemicolon(isGenerator: boolean): Block { if (token === SyntaxKind.OpenBraceToken) { return parseFunctionBlock(isGenerator, /*ignoreMissingOpenBrace:*/ false); @@ -4600,6 +4633,7 @@ module ts { // We're automatically in an ambient context if this is a .d.ts file. var inAmbientContext = fileExtensionIs(file.filename, ".d.ts"); var inFunctionBlock = false; + var inBlock = false; var parent: Node; visitNode(file); @@ -4614,6 +4648,10 @@ module ts { if (isFunctionBlock(node)) { inFunctionBlock = true; } + var savedInBlock = inBlock; + if (node.kind === SyntaxKind.Block || node.kind === SyntaxKind.TryBlock || node.kind === SyntaxKind.FinallyBlock) { + inBlock = true; + } var savedInAmbientContext = inAmbientContext if (node.flags & NodeFlags.Ambient) { @@ -4624,6 +4662,7 @@ module ts { inAmbientContext = savedInAmbientContext; inFunctionBlock = savedInFunctionBlock; + inBlock = savedInBlock; } parent = savedParent; @@ -5085,7 +5124,8 @@ module ts { } function checkFunctionDeclaration(node: FunctionLikeDeclaration) { - return checkAnySignatureDeclaration(node) || + return checkForDisallowedModifiersInBlock(node) || + checkAnySignatureDeclaration(node) || checkFunctionName(node.name) || checkForBodyInAmbientContext(node.body, /*isConstructor:*/ false) || checkForGenerator(node); @@ -5820,10 +5860,17 @@ module ts { } function checkVariableStatement(node: VariableStatement) { - return checkVariableDeclarations(node.declarations) || + return checkForDisallowedModifiersInBlock(node) || + checkVariableDeclarations(node.declarations) || checkForDisallowedLetOrConstStatement(node); } + function checkForDisallowedModifiersInBlock(node: Node) { + if (inBlock && node.modifiers) { + return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); + } + } + function checkForDisallowedLetOrConstStatement(node: VariableStatement) { if (!allowLetAndConstDeclarations(node.parent)) { if (isLet(node)) { diff --git a/tests/baselines/reference/parserModifierOnStatementInBlock2.errors.txt b/tests/baselines/reference/parserModifierOnStatementInBlock2.errors.txt index 7155d5cfe42..b07d7d9aad1 100644 --- a/tests/baselines/reference/parserModifierOnStatementInBlock2.errors.txt +++ b/tests/baselines/reference/parserModifierOnStatementInBlock2.errors.txt @@ -1,13 +1,10 @@ -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock2.ts(2,12): error TS1005: ';' expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock2.ts(2,4): error TS2304: Cannot find name 'declare'. +tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock2.ts(2,4): error TS1184: Modifiers cannot appear here. -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock2.ts (2 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock2.ts (1 errors) ==== { declare var x = this; - ~~~ -!!! error TS1005: ';' expected. ~~~~~~~ -!!! error TS2304: Cannot find name 'declare'. +!!! error TS1184: Modifiers cannot appear here. } \ No newline at end of file From 563b23424065cf3a54a01035e3028b6c9ec0ebae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 19:07:36 -0800 Subject: [PATCH 11/50] Incremental parser tests should verify the same set of diagnostics are produced. --- tests/cases/unittests/incrementalParser.ts | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index 74b67810d26..5524060615e 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -21,6 +21,26 @@ module ts { return createLanguageServiceSourceFile(/*fileName:*/ "", text, ScriptTarget.ES5, version, /*isOpen:*/ true, /*setNodeParents:*/ true) } + function assertSameDiagnostics(file1: SourceFile, file2: SourceFile) { + var diagnostics1 = file1.getSyntacticDiagnostics(); + var diagnostics2 = file2.getSyntacticDiagnostics(); + + assert.equal(diagnostics1.length, diagnostics2.length, "diagnostics1.length !== diagnostics2.length"); + for (var i = 0, n = diagnostics1.length; i < n; i++) { + var d1 = diagnostics1[i]; + var d2 = diagnostics2[i]; + + assert.equal(d1.file, file1, "d1.file !== file1"); + assert.equal(d2.file, file2, "d2.file !== file2"); + assert.equal(d1.start, d2.start, "d1.start !== d2.start"); + assert.equal(d1.length, d2.length, "d1.length !== d2.length"); + assert.equal(d1.messageText, d2.messageText, "d1.messageText !== d2.messageText"); + assert.equal(d1.category, d2.category, "d1.category !== d2.category"); + assert.equal(d1.code, d2.code, "d1.code !== d2.code"); + assert.equal(d1.isEarly, d2.isEarly, "d1.isEarly !== d2.isEarly"); + } + } + // NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new // tree. It may change as we tweak the parser. If the count increases then that should always // be a good thing. If it decreases, that's not great (less reusability), but that may be @@ -41,6 +61,9 @@ module ts { // We should get the same tree when doign a full or incremental parse. assertStructuralEquals(newTree, incrementalNewTree); + // We should also get the exact same set of diagnostics. + assertSameDiagnostics(newTree, incrementalNewTree); + // There should be no reused nodes between two trees that are fully parsed. assert.isTrue(reusedElements(oldTree, newTree) === 0); @@ -694,6 +717,42 @@ module m3 { }\ compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); }); + it('Class to interface',() => { + var source = "class A { public M1() { } public M2() { } public M3() { } p1 = 0; p2 = 0; p3 = 0 }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withChange(oldText, 0, "class".length, "interface"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + + it('Interface to class',() => { + var source = "interface A { M1?(); M2?(); M3?(); p1?; p2?; p3? }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withChange(oldText, 0, "interface".length, "class"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + + it('Surrounding function declarations with block',() => { + var source = "declare function F1() { } export function F2() { } declare export function F3() { }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withInsert(oldText, 0, "{"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + + it('Removing block around function declarations',() => { + var source = "{ declare function F1() { } export function F2() { } declare export function F3() { }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withDelete(oldText, 0, "{".length); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + // Simulated typing tests. it('Type extends clause 1',() => { From ee828dc1da3fbe3fe2164465e5887481f10fb8ac Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 19:15:44 -0800 Subject: [PATCH 12/50] More incremental parser tests. --- src/compiler/types.ts | 1 + tests/cases/unittests/incrementalParser.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8398387094a..ab4571bfb9a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -866,6 +866,7 @@ module ts { filename: string; text: string; + getLineAndCharacterFromPosition(position: number): LineAndCharacter; getPositionFromLineAndCharacter(line: number, character: number): number; getLineStarts(): number[]; diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index 5524060615e..cf6509d09ad 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -67,6 +67,9 @@ module ts { // There should be no reused nodes between two trees that are fully parsed. assert.isTrue(reusedElements(oldTree, newTree) === 0); + assert.equal(newTree.filename, incrementalNewTree.filename, "newTree.filename !== incrementalNewTree.filename"); + assert.equal(newTree.text, incrementalNewTree.text, "newTree.filename !== incrementalNewTree.filename"); + if (expectedReusedElements !== -1) { var actualReusedCount = reusedElements(oldTree, incrementalNewTree); assert.equal(actualReusedCount, expectedReusedElements, actualReusedCount + " !== " + expectedReusedElements); From 4850dfbb8e7dcf97fc5ac0dd6b2bebd81c759b8f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 20:55:46 -0800 Subject: [PATCH 13/50] Support modifiers on index signatures in an object type. This makes index signature parsing non-contextual. This is necessary so that incremental parsing can reuse index signatures acros classes and object types. --- src/compiler/parser.ts | 58 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9552e0f9cc9..bd408ad24ef 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2226,7 +2226,8 @@ module ts { return token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken || token === SyntaxKind.CloseBracketToken; } - function parseIndexSignatureDeclaration(fullStart: number, modifiers: ModifiersArray): IndexSignatureDeclaration { + function parseIndexSignatureDeclaration(modifiers: ModifiersArray): IndexSignatureDeclaration { + var fullStart = modifiers ? modifiers.pos : scanner.getStartPos(); var node = createNode(SyntaxKind.IndexSignature, fullStart); setModifiers(node, modifiers); node.parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); @@ -2268,10 +2269,25 @@ module ts { case SyntaxKind.OpenBracketToken: // Both for indexers and computed properties return true; default: + if (isModifier(token)) { + var result = lookAhead(isStartOfIndexSignatureDeclaration); + if (result) { + return result; + } + } + return isLiteralPropertyName() && lookAhead(isTypeMemberWithLiteralPropertyName); } } + function isStartOfIndexSignatureDeclaration() { + while (isModifier(token)) { + nextToken(); + } + + return isIndexSignature(); + } + function isTypeMemberWithLiteralPropertyName() { nextToken(); return token === SyntaxKind.OpenParenToken || @@ -2288,7 +2304,9 @@ module ts { return parseSignatureMember(SyntaxKind.CallSignature); case SyntaxKind.OpenBracketToken: // Indexer or computed property - return isIndexSignature() ? parseIndexSignatureDeclaration(scanner.getStartPos(), /*modifiers:*/ undefined) : parsePropertyOrMethodSignature(); + return isIndexSignature() + ? parseIndexSignatureDeclaration(/*modifiers:*/ undefined) + : parsePropertyOrMethodSignature(); case SyntaxKind.NewKeyword: if (lookAhead(isStartOfConstructSignature)) { return parseSignatureMember(SyntaxKind.ConstructSignature); @@ -2298,12 +2316,32 @@ module ts { case SyntaxKind.NumericLiteral: return parsePropertyOrMethodSignature(); default: + // Index declaration as allowed as a type member. But as per the grammar, + // they also allow modifiers. So we have to check for an index declaration + // that might be following modifiers. This ensures that things work properly + // when incrementally parsing as the parser will produce the Index declaration + // if it has the same text regardless of whether it is inside a class or an + // object type. + if (isModifier(token)) { + var result = tryParse(parseIndexSignatureWithModifiers); + if (result) { + return result; + } + } + if (isIdentifierOrKeyword()) { return parsePropertyOrMethodSignature(); } } } + function parseIndexSignatureWithModifiers() { + var modifiers = parseModifiers(); + return isIndexSignature() + ? parseIndexSignatureDeclaration(modifiers) + : undefined; + } + function isStartOfConstructSignature() { nextToken(); return token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken; @@ -3866,6 +3904,12 @@ module ts { } // Else parse it like identifier - fall through default: + // Functions and variable statements are allowed as a statement. But as per + // the grammar, they also allow modifiers. So we have to check for those + // statements that might be following modifiers. This ensures that things + // work properly when incrementally parsing as the parser will produce the + // same FunctionDeclaraiton or VariableStatement if it has the same text + // regardless of whether it is inside a block or not. if (isModifier(token)) { var result = tryParse(parseVariableStatementOrFunctionDeclarationWithModifiers); if (result) { @@ -4177,12 +4221,16 @@ module ts { return parseConstructorDeclaration(fullStart, modifiers); } if (isIndexSignature()) { - return parseIndexSignatureDeclaration(fullStart, modifiers); + return parseIndexSignatureDeclaration(modifiers); } // It is very important that we check this *after* checking indexers because // the [ token can start an index signature or a computed property name - if (isIdentifierOrKeyword() || token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral || - token === SyntaxKind.AsteriskToken || token === SyntaxKind.OpenBracketToken) { + if (isIdentifierOrKeyword() || + token === SyntaxKind.StringLiteral || + token === SyntaxKind.NumericLiteral || + token === SyntaxKind.AsteriskToken || + token === SyntaxKind.OpenBracketToken) { + return parsePropertyOrMethodDeclaration(fullStart, modifiers); } From fe57f3d2e4be48c69756b8aaa5ae2dab2be7f5fb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 22:30:40 -0800 Subject: [PATCH 14/50] Support modifiers on object literal methods and accessors, and question tokens on object literal methods. This makes parsing of these constructs the same whether they are in an object literal or a class. This is important for incrementla parsing for knowing if we can reuse these nodes if we run into them. --- src/compiler/checker.ts | 32 +++---- src/compiler/emitter.ts | 4 +- src/compiler/parser.ts | 88 ++++++++++++------- .../FunctionDeclaration5_es6.errors.txt | 5 +- ...turesWithParameterInitializers2.errors.txt | 5 +- .../reference/dottedModuleName.errors.txt | 5 +- ...fiersOnInterfaceIndexSignature1.errors.txt | 9 ++ ...jectLiteralMemberWithModifiers1.errors.txt | 7 ++ ...jectLiteralMemberWithModifiers2.errors.txt | 7 ++ ...tLiteralMemberWithQuestionMark1.errors.txt | 7 ++ ...bjectLiteralMemberWithoutBlock1.errors.txt | 7 ++ ...ectTypesWithOptionalProperties2.errors.txt | 8 +- .../reference/parserAccessors10.errors.txt | 14 +-- .../parserComputedPropertyName5.errors.txt | 20 +---- ...EqualsGreaterThanAfterFunction1.errors.txt | 7 +- ...EqualsGreaterThanAfterFunction2.errors.txt | 5 +- ...serErrorRecovery_ParameterList6.errors.txt | 5 +- .../parserSkippedTokens16.errors.txt | 5 +- .../reference/privateIndexer2.errors.txt | 43 ++------- .../modifiersOnInterfaceIndexSignature1.ts | 3 + .../objectLiteralMemberWithModifiers1.ts | 1 + .../objectLiteralMemberWithModifiers2.ts | 1 + .../objectLiteralMemberWithQuestionMark1.ts | 1 + .../objectLiteralMemberWithoutBlock1.ts | 1 + 24 files changed, 146 insertions(+), 144 deletions(-) create mode 100644 tests/baselines/reference/modifiersOnInterfaceIndexSignature1.errors.txt create mode 100644 tests/baselines/reference/objectLiteralMemberWithModifiers1.errors.txt create mode 100644 tests/baselines/reference/objectLiteralMemberWithModifiers2.errors.txt create mode 100644 tests/baselines/reference/objectLiteralMemberWithQuestionMark1.errors.txt create mode 100644 tests/baselines/reference/objectLiteralMemberWithoutBlock1.errors.txt create mode 100644 tests/cases/compiler/modifiersOnInterfaceIndexSignature1.ts create mode 100644 tests/cases/compiler/objectLiteralMemberWithModifiers1.ts create mode 100644 tests/cases/compiler/objectLiteralMemberWithModifiers2.ts create mode 100644 tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts create mode 100644 tests/cases/compiler/objectLiteralMemberWithoutBlock1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1e8a87bc4d4..7bc8a28a795 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -695,7 +695,7 @@ module ts { var members = node.members; for (var i = 0; i < members.length; i++) { var member = members[i]; - if (member.kind === SyntaxKind.Constructor && (member).body) { + if (member.kind === SyntaxKind.Constructor && !isMissingNode((member).body)) { return member; } } @@ -2654,7 +2654,7 @@ module ts { returnType = getAnnotatedAccessorType(setter); } - if (!returnType && !(declaration).body) { + if (!returnType && isMissingNode((declaration).body)) { returnType = anyType; } } @@ -6372,7 +6372,7 @@ module ts { } // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. - if (!func.body || func.body.kind !== SyntaxKind.Block) { + if (isMissingNode(func.body) || func.body.kind !== SyntaxKind.Block) { return; } @@ -7030,7 +7030,7 @@ module ts { var func = getContainingFunction(node); if (node.flags & (NodeFlags.Public | NodeFlags.Private | NodeFlags.Protected)) { func = getContainingFunction(node); - if (!(func.kind === SyntaxKind.Constructor && func.body)) { + if (!(func.kind === SyntaxKind.Constructor && !isMissingNode(func.body))) { error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); } } @@ -7127,7 +7127,7 @@ module ts { } // exit early in the case of signature - super checks are not relevant to them - if (!node.body) { + if (isMissingNode(node.body)) { return; } @@ -7201,7 +7201,7 @@ module ts { function checkAccessorDeclaration(node: AccessorDeclaration) { if (fullTypeCheck) { if (node.kind === SyntaxKind.GetAccessor) { - if (!isInAmbientContext(node) && node.body && !(bodyContainsAReturnStatement(node.body) || bodyContainsSingleThrowStatement(node.body))) { + if (!isInAmbientContext(node) && !isMissingNode(node.body) && !(bodyContainsAReturnStatement(node.body) || bodyContainsSingleThrowStatement(node.body))) { error(node.name, Diagnostics.A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement); } } @@ -7290,7 +7290,7 @@ module ts { // TypeScript 1.0 spec (April 2014): 3.7.2.2 // Specialized signatures are not permitted in conjunction with a function body - if ((signatureDeclarationNode).body) { + if (!isMissingNode((signatureDeclarationNode).body)) { error(signatureDeclarationNode, Diagnostics.A_signature_with_an_implementation_cannot_use_a_string_literal_type); return; } @@ -7423,7 +7423,7 @@ module ts { error(errorNode, diagnostic); return; } - else if ((subsequentNode).body) { + else if (!isMissingNode((subsequentNode).body)) { error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); return; } @@ -7465,7 +7465,7 @@ module ts { someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); - if (node.body && bodyDeclaration) { + if (!isMissingNode(node.body) && bodyDeclaration) { if (isConstructor) { multipleConstructorImplementation = true; } @@ -7477,7 +7477,7 @@ module ts { reportImplementationExpectedError(previousDeclaration); } - if (node.body) { + if (!isMissingNode(node.body)) { if (!bodyDeclaration) { bodyDeclaration = node; } @@ -7660,7 +7660,7 @@ module ts { // Report an implicit any error if there is no body, no explicit return type, and node is not a private method // in an ambient context - if (compilerOptions.noImplicitAny && !node.body && !node.type && !isPrivateWithinAmbient(node)) { + if (compilerOptions.noImplicitAny && isMissingNode(node.body) && !node.type && !isPrivateWithinAmbient(node)) { reportImplicitAnyError(node, anyType); } } @@ -7674,7 +7674,7 @@ module ts { function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { // no rest parameters \ declaration context \ overload - no codegen impact - if (!hasRestParameters(node) || isInAmbientContext(node) || !(node).body) { + if (!hasRestParameters(node) || isInAmbientContext(node) || isMissingNode((node).body)) { return; } @@ -7706,7 +7706,7 @@ module ts { } var root = getRootDeclaration(node); - if (root.kind === SyntaxKind.Parameter && !(root.parent).body) { + if (root.kind === SyntaxKind.Parameter && isMissingNode((root.parent).body)) { // just an overload - no codegen impact return false; } @@ -7866,7 +7866,7 @@ module ts { forEach((node.name).elements, checkSourceElement); } // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body - if (node.initializer && getRootDeclaration(node).kind === SyntaxKind.Parameter && !getContainingFunction(node).body) { + if (node.initializer && getRootDeclaration(node).kind === SyntaxKind.Parameter && isMissingNode(getContainingFunction(node).body)) { error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); return; } @@ -8612,7 +8612,7 @@ module ts { var declarations = symbol.declarations; for (var i = 0; i < declarations.length; i++) { var declaration = declarations[i]; - if ((declaration.kind === SyntaxKind.ClassDeclaration || (declaration.kind === SyntaxKind.FunctionDeclaration && (declaration).body)) && !isInAmbientContext(declaration)) { + if ((declaration.kind === SyntaxKind.ClassDeclaration || (declaration.kind === SyntaxKind.FunctionDeclaration && !isMissingNode((declaration).body))) && !isInAmbientContext(declaration)) { return declaration; } } @@ -9499,7 +9499,7 @@ module ts { } function isImplementationOfOverload(node: FunctionLikeDeclaration) { - if (node.body) { + if (!isMissingNode(node.body)) { var symbol = getSymbolOfNode(node); var signaturesOfSymbol = getSignaturesOfSymbol(symbol); // If this function body corresponds to function with multiple signature, it is implementation of overload diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 9428f94bb52..24890799706 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -268,7 +268,7 @@ module ts { function getFirstConstructorWithBody(node: ClassDeclaration): ConstructorDeclaration { return forEach(node.members, member => { - if (member.kind === SyntaxKind.Constructor && (member).body) { + if (member.kind === SyntaxKind.Constructor && !isMissingNode((member).body)) { return member; } }); @@ -3106,7 +3106,7 @@ module ts { } function emitFunctionDeclaration(node: FunctionLikeDeclaration) { - if (!node.body) { + if (isMissingNode(node.body)) { return emitPinnedOrTripleSlashComments(node); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index bd408ad24ef..5729e29c0e5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -61,6 +61,10 @@ module ts { } export function isMissingNode(node: Node) { + if (node === undefined) { + return true; + } + return node.pos === node.end && node.kind !== SyntaxKind.EndOfFileToken; } @@ -1377,7 +1381,7 @@ module ts { return inStrictModeContext() ? token > SyntaxKind.LastFutureReservedWord : token > SyntaxKind.LastReservedWord; } - function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): boolean { + function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage): boolean { if (token === kind) { nextToken(); return true; @@ -1385,7 +1389,7 @@ module ts { // Report specific message if provided with one. Otherwise, report generic fallback message. if (diagnosticMessage) { - parseErrorAtCurrentToken(diagnosticMessage, arg0); + parseErrorAtCurrentToken(diagnosticMessage); } else { parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); @@ -1420,7 +1424,7 @@ module ts { return token === SyntaxKind.CloseBraceToken || token === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak(); } - function parseSemicolon(diagnosticMessage?: DiagnosticMessage): boolean { + function parseSemicolon(): boolean { if (canParseSemicolon()) { if (token === SyntaxKind.SemicolonToken) { // consume the semicolon if it was explicitly provided. @@ -1430,7 +1434,7 @@ module ts { return true; } else { - return parseExpected(SyntaxKind.SemicolonToken, diagnosticMessage); + return parseExpected(SyntaxKind.SemicolonToken); } } @@ -3438,21 +3442,22 @@ module ts { var fullStart = scanner.getStartPos(); var initialToken = token; + var modifiers = parseModifiers(); if (parseContextualModifier(SyntaxKind.GetKeyword) || parseContextualModifier(SyntaxKind.SetKeyword)) { var kind = initialToken === SyntaxKind.GetKeyword ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; - return parseAccessorDeclaration(kind, fullStart, /*modifiers*/undefined); + return parseAccessorDeclaration(kind, fullStart, modifiers); } var asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); var tokenIsIdentifier = isIdentifier(); var nameToken = token; var propertyName = parsePropertyName(); - if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(fullStart, /*modifiers:*/ undefined, asteriskToken, propertyName, /*questionToken:*/ undefined, /*requireBlock:*/ true); - } // Disallowing of optional property assignments happens in the grammar checker. var questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { + return parseMethodDeclaration(fullStart, modifiers, asteriskToken, propertyName, questionToken); + } // Parse to check if it is short-hand property assignment or normal property assignment if ((token === SyntaxKind.CommaToken || token === SyntaxKind.CloseBraceToken) && tokenIsIdentifier) { @@ -3514,9 +3519,9 @@ module ts { } // STATEMENTS - function parseBlock(kind: SyntaxKind, ignoreMissingOpenBrace: boolean, checkForStrictMode: boolean): Block { + function parseBlock(kind: SyntaxKind, ignoreMissingOpenBrace: boolean, checkForStrictMode: boolean, diagnosticMessage?: DiagnosticMessage): Block { var node = createNode(kind); - if (parseExpected(SyntaxKind.OpenBraceToken) || ignoreMissingOpenBrace) { + if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { node.statements = parseList(ParsingContext.BlockStatements, checkForStrictMode, parseStatement); parseExpected(SyntaxKind.CloseBraceToken); } @@ -3526,11 +3531,11 @@ module ts { return finishNode(node); } - function parseFunctionBlock(allowYield: boolean, ignoreMissingOpenBrace: boolean): Block { + function parseFunctionBlock(allowYield: boolean, ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { var savedYieldContext = inYieldContext(); setYieldContext(allowYield); - var block = parseBlock(SyntaxKind.Block, ignoreMissingOpenBrace, /*checkForStrictMode*/ true); + var block = parseBlock(SyntaxKind.Block, ignoreMissingOpenBrace, /*checkForStrictMode*/ true, diagnosticMessage); setYieldContext(savedYieldContext); @@ -3949,13 +3954,13 @@ module ts { return undefined; } - function parseFunctionBlockOrSemicolon(isGenerator: boolean): Block { - if (token === SyntaxKind.OpenBraceToken) { - return parseFunctionBlock(isGenerator, /*ignoreMissingOpenBrace:*/ false); + function parseFunctionBlockOrSemicolon(isGenerator: boolean, diagnosticMessage?: DiagnosticMessage): Block { + if (token !== SyntaxKind.OpenBraceToken && canParseSemicolon()) { + parseSemicolon(); + return; } - parseSemicolon(Diagnostics.or_expected); - return undefined; + return parseFunctionBlock(isGenerator, /*ignoreMissingOpenBrace:*/ false, diagnosticMessage); } // DECLARATIONS @@ -4070,7 +4075,7 @@ module ts { node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); node.name = parseIdentifier(); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ !!node.asteriskToken, /*requireCompleteParameterList:*/ false, node); - node.body = parseFunctionBlockOrSemicolon(!!node.asteriskToken); + node.body = parseFunctionBlockOrSemicolon(!!node.asteriskToken, Diagnostics.or_expected); return finishNode(node); } @@ -4079,18 +4084,18 @@ module ts { setModifiers(node, modifiers); parseExpected(SyntaxKind.ConstructorKeyword); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ false, node); - node.body = parseFunctionBlockOrSemicolon(/*isGenerator:*/ false); + node.body = parseFunctionBlockOrSemicolon(/*isGenerator:*/ false, Diagnostics.or_expected); return finishNode(node); } - function parseMethodDeclaration(fullStart: number, modifiers: ModifiersArray, asteriskToken: Node, name: DeclarationName, questionToken: Node, requireBlock: boolean): MethodDeclaration { + function parseMethodDeclaration(fullStart: number, modifiers: ModifiersArray, asteriskToken: Node, name: DeclarationName, questionToken: Node, diagnosticMessage?: DiagnosticMessage): MethodDeclaration { var method = createNode(SyntaxKind.MethodDeclaration, fullStart); setModifiers(method, modifiers); method.asteriskToken = asteriskToken; method.name = name; method.questionToken = questionToken; fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ !!asteriskToken, /*requireCompleteParameterList:*/ false, method); - method.body = requireBlock ? parseFunctionBlock(!!asteriskToken, /*ignoreMissingOpenBrace:*/ false) : parseFunctionBlockOrSemicolon(!!asteriskToken); + method.body = parseFunctionBlockOrSemicolon(!!asteriskToken, diagnosticMessage); return finishNode(method); } @@ -4102,7 +4107,7 @@ module ts { // report an error in the grammar checker. var questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(fullStart, modifiers, asteriskToken, name, questionToken, /*requireBlock:*/ false); + return parseMethodDeclaration(fullStart, modifiers, asteriskToken, name, questionToken, Diagnostics.or_expected); } else { var property = createNode(SyntaxKind.PropertyDeclaration, fullStart); @@ -4681,6 +4686,7 @@ module ts { // We're automatically in an ambient context if this is a .d.ts file. var inAmbientContext = fileExtensionIs(file.filename, ".d.ts"); var inFunctionBlock = false; + var inObjectLiteralExpression = false; var inBlock = false; var parent: Node; visitNode(file); @@ -4700,7 +4706,10 @@ module ts { if (node.kind === SyntaxKind.Block || node.kind === SyntaxKind.TryBlock || node.kind === SyntaxKind.FinallyBlock) { inBlock = true; } - + var savedInObjectLiteralExpression = inObjectLiteralExpression; + if (node.kind === SyntaxKind.ObjectLiteralExpression) { + inObjectLiteralExpression = true; + } var savedInAmbientContext = inAmbientContext if (node.flags & NodeFlags.Ambient) { inAmbientContext = true; @@ -4711,6 +4720,7 @@ module ts { inAmbientContext = savedInAmbientContext; inFunctionBlock = savedInFunctionBlock; inBlock = savedInBlock; + inObjectLiteralExpression = savedInObjectLiteralExpression; } parent = savedParent; @@ -5172,7 +5182,7 @@ module ts { } function checkFunctionDeclaration(node: FunctionLikeDeclaration) { - return checkForDisallowedModifiersInBlock(node) || + return checkForDisallowedModifiersInBlockOrObjectLiteral(node) || checkAnySignatureDeclaration(node) || checkFunctionName(node.name) || checkForBodyInAmbientContext(node.body, /*isConstructor:*/ false) || @@ -5200,7 +5210,8 @@ module ts { } function checkGetAccessor(node: MethodDeclaration) { - return checkAnySignatureDeclaration(node) || + return checkForDisallowedModifiersInBlockOrObjectLiteral(node) || + checkAnySignatureDeclaration(node) || checkAccessor(node); } @@ -5298,12 +5309,22 @@ module ts { } function checkMethod(node: MethodDeclaration) { - if (checkAnySignatureDeclaration(node) || + if (checkForDisallowedModifiersInBlockOrObjectLiteral(node) || + checkAnySignatureDeclaration(node) || checkForBodyInAmbientContext(node.body, /*isConstructor:*/ false) || checkForGenerator(node)) { return true; } + if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) { + if (checkForInvalidQuestionMark(node, node.questionToken, Diagnostics.A_class_member_cannot_be_declared_optional)) { + return true; + } + else if (node.body === undefined) { + return grammarErrorAtPos(node.end - 1, ";".length, Diagnostics._0_expected, "{"); + } + } + if (node.parent.kind === SyntaxKind.ClassDeclaration) { if (checkForInvalidQuestionMark(node, node.questionToken, Diagnostics.A_class_member_cannot_be_declared_optional)) { return true; @@ -5316,7 +5337,7 @@ module ts { if (inAmbientContext) { return checkForDisallowedComputedProperty(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_an_ambient_context); } - else if (!node.body) { + else if (isMissingNode(node.body)) { return checkForDisallowedComputedProperty(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_method_overloads); } } @@ -5711,7 +5732,8 @@ module ts { } function checkSetAccessor(node: MethodDeclaration) { - return checkAnySignatureDeclaration(node) || + return checkForDisallowedModifiersInBlockOrObjectLiteral(node) || + checkAnySignatureDeclaration(node) || checkAccessor(node); } @@ -5908,14 +5930,16 @@ module ts { } function checkVariableStatement(node: VariableStatement) { - return checkForDisallowedModifiersInBlock(node) || + return checkForDisallowedModifiersInBlockOrObjectLiteral(node) || checkVariableDeclarations(node.declarations) || checkForDisallowedLetOrConstStatement(node); } - function checkForDisallowedModifiersInBlock(node: Node) { - if (inBlock && node.modifiers) { - return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); + function checkForDisallowedModifiersInBlockOrObjectLiteral(node: Node) { + if (node.modifiers) { + if (inBlock || inObjectLiteralExpression) { + return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); + } } } diff --git a/tests/baselines/reference/FunctionDeclaration5_es6.errors.txt b/tests/baselines/reference/FunctionDeclaration5_es6.errors.txt index 9a8be921795..490f3bd9f22 100644 --- a/tests/baselines/reference/FunctionDeclaration5_es6.errors.txt +++ b/tests/baselines/reference/FunctionDeclaration5_es6.errors.txt @@ -1,17 +1,14 @@ tests/cases/conformance/es6/functionDeclarations/FunctionDeclaration5_es6.ts(1,14): error TS1138: Parameter declaration expected. tests/cases/conformance/es6/functionDeclarations/FunctionDeclaration5_es6.ts(1,19): error TS1005: ';' expected. -tests/cases/conformance/es6/functionDeclarations/FunctionDeclaration5_es6.ts(1,10): error TS2391: Function implementation is missing or not immediately following the declaration. tests/cases/conformance/es6/functionDeclarations/FunctionDeclaration5_es6.ts(1,14): error TS2304: Cannot find name 'yield'. -==== tests/cases/conformance/es6/functionDeclarations/FunctionDeclaration5_es6.ts (4 errors) ==== +==== tests/cases/conformance/es6/functionDeclarations/FunctionDeclaration5_es6.ts (3 errors) ==== function*foo(yield) { ~~~~~ !!! error TS1138: Parameter declaration expected. ~ !!! error TS1005: ';' expected. - ~~~ -!!! error TS2391: Function implementation is missing or not immediately following the declaration. ~~~~~ !!! error TS2304: Cannot find name 'yield'. } \ No newline at end of file diff --git a/tests/baselines/reference/callSignaturesWithParameterInitializers2.errors.txt b/tests/baselines/reference/callSignaturesWithParameterInitializers2.errors.txt index 1801bd42e4d..c307f8ad5cf 100644 --- a/tests/baselines/reference/callSignaturesWithParameterInitializers2.errors.txt +++ b/tests/baselines/reference/callSignaturesWithParameterInitializers2.errors.txt @@ -2,10 +2,11 @@ tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWit tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWithParameterInitializers2.ts(4,14): error TS2371: A parameter initializer is only allowed in a function or constructor implementation. tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWithParameterInitializers2.ts(11,9): error TS2371: A parameter initializer is only allowed in a function or constructor implementation. tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWithParameterInitializers2.ts(20,5): error TS2300: Duplicate identifier 'foo'. +tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWithParameterInitializers2.ts(20,9): error TS2371: A parameter initializer is only allowed in a function or constructor implementation. tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWithParameterInitializers2.ts(21,5): error TS2300: Duplicate identifier 'foo'. -==== tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWithParameterInitializers2.ts (5 errors) ==== +==== tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWithParameterInitializers2.ts (6 errors) ==== // Optional parameters allow initializers only in implementation signatures // All the below declarations are errors @@ -34,6 +35,8 @@ tests/cases/conformance/types/objectTypeLiteral/callSignatures/callSignaturesWit !!! error TS1005: '{' expected. ~~~ !!! error TS2300: Duplicate identifier 'foo'. + ~~~~~ +!!! error TS2371: A parameter initializer is only allowed in a function or constructor implementation. foo(x = 1) { }, // error ~~~ !!! error TS2300: Duplicate identifier 'foo'. diff --git a/tests/baselines/reference/dottedModuleName.errors.txt b/tests/baselines/reference/dottedModuleName.errors.txt index 9d9de6ee230..afb8b944355 100644 --- a/tests/baselines/reference/dottedModuleName.errors.txt +++ b/tests/baselines/reference/dottedModuleName.errors.txt @@ -1,16 +1,13 @@ tests/cases/compiler/dottedModuleName.ts(3,29): error TS1144: '{' or ';' expected. -tests/cases/compiler/dottedModuleName.ts(3,18): error TS2391: Function implementation is missing or not immediately following the declaration. tests/cases/compiler/dottedModuleName.ts(3,33): error TS2304: Cannot find name 'x'. -==== tests/cases/compiler/dottedModuleName.ts (3 errors) ==== +==== tests/cases/compiler/dottedModuleName.ts (2 errors) ==== module M { export module N { export function f(x:number)=>2*x; ~~ !!! error TS1144: '{' or ';' expected. - ~ -!!! error TS2391: Function implementation is missing or not immediately following the declaration. ~ !!! error TS2304: Cannot find name 'x'. export module X.Y.Z { diff --git a/tests/baselines/reference/modifiersOnInterfaceIndexSignature1.errors.txt b/tests/baselines/reference/modifiersOnInterfaceIndexSignature1.errors.txt new file mode 100644 index 00000000000..b325710a9f0 --- /dev/null +++ b/tests/baselines/reference/modifiersOnInterfaceIndexSignature1.errors.txt @@ -0,0 +1,9 @@ +tests/cases/compiler/modifiersOnInterfaceIndexSignature1.ts(2,3): error TS1145: Modifiers not permitted on index signature members. + + +==== tests/cases/compiler/modifiersOnInterfaceIndexSignature1.ts (1 errors) ==== + interface I { + public [a: string]: number; + ~~~~~~ +!!! error TS1145: Modifiers not permitted on index signature members. + } \ No newline at end of file diff --git a/tests/baselines/reference/objectLiteralMemberWithModifiers1.errors.txt b/tests/baselines/reference/objectLiteralMemberWithModifiers1.errors.txt new file mode 100644 index 00000000000..9e739194f54 --- /dev/null +++ b/tests/baselines/reference/objectLiteralMemberWithModifiers1.errors.txt @@ -0,0 +1,7 @@ +tests/cases/compiler/objectLiteralMemberWithModifiers1.ts(1,11): error TS1184: Modifiers cannot appear here. + + +==== tests/cases/compiler/objectLiteralMemberWithModifiers1.ts (1 errors) ==== + var v = { public foo() { } } + ~~~~~~ +!!! error TS1184: Modifiers cannot appear here. \ No newline at end of file diff --git a/tests/baselines/reference/objectLiteralMemberWithModifiers2.errors.txt b/tests/baselines/reference/objectLiteralMemberWithModifiers2.errors.txt new file mode 100644 index 00000000000..9b34ea61733 --- /dev/null +++ b/tests/baselines/reference/objectLiteralMemberWithModifiers2.errors.txt @@ -0,0 +1,7 @@ +tests/cases/compiler/objectLiteralMemberWithModifiers2.ts(1,11): error TS1184: Modifiers cannot appear here. + + +==== tests/cases/compiler/objectLiteralMemberWithModifiers2.ts (1 errors) ==== + var v = { public get foo() { } } + ~~~~~~ +!!! error TS1184: Modifiers cannot appear here. \ No newline at end of file diff --git a/tests/baselines/reference/objectLiteralMemberWithQuestionMark1.errors.txt b/tests/baselines/reference/objectLiteralMemberWithQuestionMark1.errors.txt new file mode 100644 index 00000000000..b10ba701143 --- /dev/null +++ b/tests/baselines/reference/objectLiteralMemberWithQuestionMark1.errors.txt @@ -0,0 +1,7 @@ +tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts(1,14): error TS1112: A class member cannot be declared optional. + + +==== tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts (1 errors) ==== + var v = { foo?() { } } + ~ +!!! error TS1112: A class member cannot be declared optional. \ No newline at end of file diff --git a/tests/baselines/reference/objectLiteralMemberWithoutBlock1.errors.txt b/tests/baselines/reference/objectLiteralMemberWithoutBlock1.errors.txt new file mode 100644 index 00000000000..fa5fb104c82 --- /dev/null +++ b/tests/baselines/reference/objectLiteralMemberWithoutBlock1.errors.txt @@ -0,0 +1,7 @@ +tests/cases/compiler/objectLiteralMemberWithoutBlock1.ts(1,16): error TS1005: '{' expected. + + +==== tests/cases/compiler/objectLiteralMemberWithoutBlock1.ts (1 errors) ==== + var v = { foo(); } + ~ +!!! error TS1005: '{' expected. \ No newline at end of file diff --git a/tests/baselines/reference/objectTypesWithOptionalProperties2.errors.txt b/tests/baselines/reference/objectTypesWithOptionalProperties2.errors.txt index 533834baea7..761fba18b15 100644 --- a/tests/baselines/reference/objectTypesWithOptionalProperties2.errors.txt +++ b/tests/baselines/reference/objectTypesWithOptionalProperties2.errors.txt @@ -11,11 +11,9 @@ tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWith tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties2.ts(25,8): error TS1005: '{' expected. tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties2.ts(25,9): error TS1136: Property assignment expected. tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties2.ts(26,1): error TS1005: ':' expected. -tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties2.ts(12,5): error TS2391: Function implementation is missing or not immediately following the declaration. -tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties2.ts(20,5): error TS2391: Function implementation is missing or not immediately following the declaration. -==== tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties2.ts (15 errors) ==== +==== tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties2.ts (13 errors) ==== // Illegal attempts to define optional methods var a: { @@ -40,8 +38,6 @@ tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWith !!! error TS1144: '{' or ';' expected. ~ !!! error TS1068: Unexpected token. A constructor, method, accessor, or property was expected. - ~ -!!! error TS2391: Function implementation is missing or not immediately following the declaration. } interface I2 { @@ -58,8 +54,6 @@ tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWith !!! error TS1144: '{' or ';' expected. ~ !!! error TS1068: Unexpected token. A constructor, method, accessor, or property was expected. - ~ -!!! error TS2391: Function implementation is missing or not immediately following the declaration. } diff --git a/tests/baselines/reference/parserAccessors10.errors.txt b/tests/baselines/reference/parserAccessors10.errors.txt index dcd819c4880..3c4981ddc45 100644 --- a/tests/baselines/reference/parserAccessors10.errors.txt +++ b/tests/baselines/reference/parserAccessors10.errors.txt @@ -1,15 +1,9 @@ -tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts(2,10): error TS1005: ':' expected. -tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts(2,14): error TS1005: ',' expected. -tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts(2,10): error TS2304: Cannot find name 'get'. +tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts(2,3): error TS1184: Modifiers cannot appear here. -==== tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts (3 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts (1 errors) ==== var v = { public get foo() { } - ~~~ -!!! error TS1005: ':' expected. - ~~~ -!!! error TS1005: ',' expected. - ~~~ -!!! error TS2304: Cannot find name 'get'. + ~~~~~~ +!!! error TS1184: Modifiers cannot appear here. }; \ No newline at end of file diff --git a/tests/baselines/reference/parserComputedPropertyName5.errors.txt b/tests/baselines/reference/parserComputedPropertyName5.errors.txt index 149550c93ff..756a133111b 100644 --- a/tests/baselines/reference/parserComputedPropertyName5.errors.txt +++ b/tests/baselines/reference/parserComputedPropertyName5.errors.txt @@ -1,19 +1,7 @@ -tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName5.ts(1,18): error TS1005: ':' expected. -tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName5.ts(1,28): error TS1005: ',' expected. -tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName5.ts(1,32): error TS1128: Declaration or statement expected. -tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName5.ts(1,18): error TS2304: Cannot find name 'get'. -tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName5.ts(1,23): error TS2304: Cannot find name 'e'. +tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName5.ts(1,11): error TS1184: Modifiers cannot appear here. -==== tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName5.ts (5 errors) ==== +==== tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName5.ts (1 errors) ==== var v = { public get [e]() { } }; - ~~~ -!!! error TS1005: ':' expected. - ~ -!!! error TS1005: ',' expected. - ~ -!!! error TS1128: Declaration or statement expected. - ~~~ -!!! error TS2304: Cannot find name 'get'. - ~ -!!! error TS2304: Cannot find name 'e'. \ No newline at end of file + ~~~~~~ +!!! error TS1184: Modifiers cannot appear here. \ No newline at end of file diff --git a/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction1.errors.txt b/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction1.errors.txt index 7c32d4433e4..4f4c37c78be 100644 --- a/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction1.errors.txt +++ b/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction1.errors.txt @@ -1,10 +1,7 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction1.ts(1,14): error TS1144: '{' or ';' expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction1.ts(1,10): error TS2391: Function implementation is missing or not immediately following the declaration. -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction1.ts (2 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction1.ts (1 errors) ==== function f() => 4; ~~ -!!! error TS1144: '{' or ';' expected. - ~ -!!! error TS2391: Function implementation is missing or not immediately following the declaration. \ No newline at end of file +!!! error TS1144: '{' or ';' expected. \ No newline at end of file diff --git a/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction2.errors.txt b/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction2.errors.txt index 8df3b0bbff6..74f538a0e07 100644 --- a/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction2.errors.txt +++ b/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction2.errors.txt @@ -1,15 +1,12 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction2.ts(1,18): error TS1144: '{' or ';' expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction2.ts(1,10): error TS2391: Function implementation is missing or not immediately following the declaration. tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction2.ts(1,15): error TS2304: Cannot find name 'A'. tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction2.ts(1,21): error TS2304: Cannot find name 'p'. -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction2.ts (4 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantEqualsGreaterThanAfterFunction2.ts (3 errors) ==== function f(p: A) => p; ~~ !!! error TS1144: '{' or ';' expected. - ~ -!!! error TS2391: Function implementation is missing or not immediately following the declaration. ~ !!! error TS2304: Cannot find name 'A'. ~ diff --git a/tests/baselines/reference/parserErrorRecovery_ParameterList6.errors.txt b/tests/baselines/reference/parserErrorRecovery_ParameterList6.errors.txt index f6920f4f6a6..61ad2fb175f 100644 --- a/tests/baselines/reference/parserErrorRecovery_ParameterList6.errors.txt +++ b/tests/baselines/reference/parserErrorRecovery_ParameterList6.errors.txt @@ -1,18 +1,15 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts(2,23): error TS1110: Type expected. tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts(2,28): error TS1003: Identifier expected. tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts(3,1): error TS1128: Declaration or statement expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts(2,12): error TS2391: Function implementation is missing or not immediately following the declaration. -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts (4 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts (3 errors) ==== class Foo { public banana (x: break) { } ~~~~~ !!! error TS1110: Type expected. ~ !!! error TS1003: Identifier expected. - ~~~~~~ -!!! error TS2391: Function implementation is missing or not immediately following the declaration. } ~ !!! error TS1128: Declaration or statement expected. \ No newline at end of file diff --git a/tests/baselines/reference/parserSkippedTokens16.errors.txt b/tests/baselines/reference/parserSkippedTokens16.errors.txt index 0d1effe23c3..5d505b90112 100644 --- a/tests/baselines/reference/parserSkippedTokens16.errors.txt +++ b/tests/baselines/reference/parserSkippedTokens16.errors.txt @@ -6,10 +6,9 @@ tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens16.t tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens16.ts(8,14): error TS1109: Expression expected. tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens16.ts(1,1): error TS2304: Cannot find name 'foo'. tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens16.ts(1,8): error TS2304: Cannot find name 'Bar'. -tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens16.ts(2,10): error TS2391: Function implementation is missing or not immediately following the declaration. -==== tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens16.ts (9 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens16.ts (8 errors) ==== foo(): Bar { } ~ !!! error TS1005: ';' expected. @@ -22,8 +21,6 @@ tests/cases/conformance/parser/ecmascript5/SkippedTokens/parserSkippedTokens16.t function Foo () # { } !!! error TS1127: Invalid character. - ~~~ -!!! error TS2391: Function implementation is missing or not immediately following the declaration. 4+:5 ~ !!! error TS1109: Expression expected. diff --git a/tests/baselines/reference/privateIndexer2.errors.txt b/tests/baselines/reference/privateIndexer2.errors.txt index cff05a92965..8d3c1ec1e6d 100644 --- a/tests/baselines/reference/privateIndexer2.errors.txt +++ b/tests/baselines/reference/privateIndexer2.errors.txt @@ -1,54 +1,27 @@ -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(4,13): error TS1005: ':' expected. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(4,15): error TS1005: ',' expected. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(4,24): error TS1005: ',' expected. +tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(4,15): error TS1005: ']' expected. +tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(4,23): error TS1005: ',' expected. +tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(4,24): error TS1136: Property assignment expected. tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(4,32): error TS1005: ':' expected. tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(5,1): error TS1128: Declaration or statement expected. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(8,5): error TS1131: Property or signature expected. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(8,14): error TS1005: ']' expected. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(8,22): error TS1005: ';' expected. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(8,23): error TS1128: Declaration or statement expected. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(9,1): error TS1128: Declaration or statement expected. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(4,17): error TS2304: Cannot find name 'string'. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(8,5): error TS2304: Cannot find name 'private'. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(8,16): error TS2304: Cannot find name 'string'. -tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts(8,25): error TS2304: Cannot find name 'string'. -==== tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts (14 errors) ==== +==== tests/cases/conformance/classes/indexMemberDeclarations/privateIndexer2.ts (5 errors) ==== // private indexers not allowed var x = { private [x: string]: string; - ~ -!!! error TS1005: ':' expected. ~ +!!! error TS1005: ']' expected. + ~ !!! error TS1005: ',' expected. ~ -!!! error TS1005: ',' expected. +!!! error TS1136: Property assignment expected. ~ !!! error TS1005: ':' expected. - ~~~~~~ -!!! error TS2304: Cannot find name 'string'. } ~ !!! error TS1128: Declaration or statement expected. var y: { private[x: string]: string; - ~~~~~~~ -!!! error TS1131: Property or signature expected. - ~ -!!! error TS1005: ']' expected. - ~ -!!! error TS1005: ';' expected. - ~ -!!! error TS1128: Declaration or statement expected. - ~~~~~~~ -!!! error TS2304: Cannot find name 'private'. - ~~~~~~ -!!! error TS2304: Cannot find name 'string'. - ~~~~~~ -!!! error TS2304: Cannot find name 'string'. - } - ~ -!!! error TS1128: Declaration or statement expected. \ No newline at end of file + } \ No newline at end of file diff --git a/tests/cases/compiler/modifiersOnInterfaceIndexSignature1.ts b/tests/cases/compiler/modifiersOnInterfaceIndexSignature1.ts new file mode 100644 index 00000000000..ba5eebbefc9 --- /dev/null +++ b/tests/cases/compiler/modifiersOnInterfaceIndexSignature1.ts @@ -0,0 +1,3 @@ +interface I { + public [a: string]: number; +} \ No newline at end of file diff --git a/tests/cases/compiler/objectLiteralMemberWithModifiers1.ts b/tests/cases/compiler/objectLiteralMemberWithModifiers1.ts new file mode 100644 index 00000000000..6bdc9476fb8 --- /dev/null +++ b/tests/cases/compiler/objectLiteralMemberWithModifiers1.ts @@ -0,0 +1 @@ +var v = { public foo() { } } \ No newline at end of file diff --git a/tests/cases/compiler/objectLiteralMemberWithModifiers2.ts b/tests/cases/compiler/objectLiteralMemberWithModifiers2.ts new file mode 100644 index 00000000000..0bfa0a20521 --- /dev/null +++ b/tests/cases/compiler/objectLiteralMemberWithModifiers2.ts @@ -0,0 +1 @@ +var v = { public get foo() { } } \ No newline at end of file diff --git a/tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts b/tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts new file mode 100644 index 00000000000..0529c717d16 --- /dev/null +++ b/tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts @@ -0,0 +1 @@ +var v = { foo?() { } } \ No newline at end of file diff --git a/tests/cases/compiler/objectLiteralMemberWithoutBlock1.ts b/tests/cases/compiler/objectLiteralMemberWithoutBlock1.ts new file mode 100644 index 00000000000..7f930a5dfde --- /dev/null +++ b/tests/cases/compiler/objectLiteralMemberWithoutBlock1.ts @@ -0,0 +1 @@ +var v = { foo(); } \ No newline at end of file From ba0fd4453d8134172ab659c2abb3f45cc532c032 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 23:04:13 -0800 Subject: [PATCH 15/50] Add additional incremental tests. --- tests/cases/unittests/incrementalParser.ts | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index cf6509d09ad..c89de056941 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -756,6 +756,60 @@ module m3 { }\ compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); }); + it('Moving methods from class to object literal',() => { + var source = "class C { public A() { } public B() { } public C() { } }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + + it('Moving methods from object literal to class',() => { + var source = "var v = { public A() { } public B() { } public C() { } }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + + it('Moving index signatures from class to interface',() => { + var source = "class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withChange(oldText, 0, "class".length, "interface"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + + it('Moving index signatures from interface to class',() => { + var source = "interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withChange(oldText, 0, "interface".length, "class"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + + it('Moving accessors from class to object literal',() => { + var source = "class C { public get A() { } public get B() { } public get C() { } }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + + it('Moving accessors from object literal to class',() => { + var source = "var v = { public get A() { } public get B() { } public get C() { } }" + + var oldText = ScriptSnapshot.fromString(source); + var newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); + // Simulated typing tests. it('Type extends clause 1',() => { From 12f8bfb687f766a1e2fc4524cb63b738df48a749 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 23:33:30 -0800 Subject: [PATCH 16/50] Unify accessor declaration parsing. --- src/compiler/parser.ts | 32 +++++++++++++------ ...jectLiteralMemberWithModifiers2.errors.txt | 7 ++-- .../reference/parserAccessors10.errors.txt | 5 ++- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 5729e29c0e5..f9ffba74d0c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3438,14 +3438,24 @@ module ts { return finishNode(node); } + function tryParseAccessorDeclaration(fullStart: number, modifiers: ModifiersArray): AccessorDeclaration { + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, modifiers); + } + else if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration(SyntaxKind.SetAccessor, fullStart, modifiers); + } + + return undefined; + } + function parseObjectLiteralElement(): ObjectLiteralElement { var fullStart = scanner.getStartPos(); - var initialToken = token; - var modifiers = parseModifiers(); - if (parseContextualModifier(SyntaxKind.GetKeyword) || parseContextualModifier(SyntaxKind.SetKeyword)) { - var kind = initialToken === SyntaxKind.GetKeyword ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; - return parseAccessorDeclaration(kind, fullStart, modifiers); + + var accessor = tryParseAccessorDeclaration(fullStart, modifiers); + if (accessor) { + return accessor; } var asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); @@ -4216,18 +4226,20 @@ module ts { function parseClassElement(): ClassElement { var fullStart = getNodePos(); var modifiers = parseModifiers(); - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, modifiers); - } - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(SyntaxKind.SetAccessor, fullStart, modifiers); + + var accessor = tryParseAccessorDeclaration(fullStart, modifiers); + if (accessor) { + return accessor; } + if (token === SyntaxKind.ConstructorKeyword) { return parseConstructorDeclaration(fullStart, modifiers); } + if (isIndexSignature()) { return parseIndexSignatureDeclaration(modifiers); } + // It is very important that we check this *after* checking indexers because // the [ token can start an index signature or a computed property name if (isIdentifierOrKeyword() || diff --git a/tests/baselines/reference/objectLiteralMemberWithModifiers2.errors.txt b/tests/baselines/reference/objectLiteralMemberWithModifiers2.errors.txt index 9b34ea61733..9fe5365f7da 100644 --- a/tests/baselines/reference/objectLiteralMemberWithModifiers2.errors.txt +++ b/tests/baselines/reference/objectLiteralMemberWithModifiers2.errors.txt @@ -1,7 +1,10 @@ tests/cases/compiler/objectLiteralMemberWithModifiers2.ts(1,11): error TS1184: Modifiers cannot appear here. +tests/cases/compiler/objectLiteralMemberWithModifiers2.ts(1,22): error TS2378: A 'get' accessor must return a value or consist of a single 'throw' statement. -==== tests/cases/compiler/objectLiteralMemberWithModifiers2.ts (1 errors) ==== +==== tests/cases/compiler/objectLiteralMemberWithModifiers2.ts (2 errors) ==== var v = { public get foo() { } } ~~~~~~ -!!! error TS1184: Modifiers cannot appear here. \ No newline at end of file +!!! error TS1184: Modifiers cannot appear here. + ~~~ +!!! error TS2378: A 'get' accessor must return a value or consist of a single 'throw' statement. \ No newline at end of file diff --git a/tests/baselines/reference/parserAccessors10.errors.txt b/tests/baselines/reference/parserAccessors10.errors.txt index 3c4981ddc45..ac552b55c6a 100644 --- a/tests/baselines/reference/parserAccessors10.errors.txt +++ b/tests/baselines/reference/parserAccessors10.errors.txt @@ -1,9 +1,12 @@ tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts(2,3): error TS1184: Modifiers cannot appear here. +tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts(2,14): error TS2378: A 'get' accessor must return a value or consist of a single 'throw' statement. -==== tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts (1 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/Accessors/parserAccessors10.ts (2 errors) ==== var v = { public get foo() { } ~~~~~~ !!! error TS1184: Modifiers cannot appear here. + ~~~ +!!! error TS2378: A 'get' accessor must return a value or consist of a single 'throw' statement. }; \ No newline at end of file From b692ea9b661aa8b0fe8a07e3a30f7bd2c6580abd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Dec 2014 14:40:25 -0800 Subject: [PATCH 17/50] Remove TryBlock and FinallyBlock. They break the rule that syntactically identical constructs use the same syntax kind. This prevents node reuse in incremental parsing. --- src/compiler/binder.ts | 2 - src/compiler/checker.ts | 4 -- src/compiler/emitter.ts | 2 - src/compiler/parser.ts | 38 +++++++---------- src/compiler/types.ts | 2 - src/services/breakpoints.ts | 4 -- src/services/formatting.ts | 5 --- src/services/formatting/rules.ts | 5 --- src/services/outliningElementsCollector.ts | 42 +++++++++++++------ src/services/services.ts | 15 +++++-- src/services/smartIndenter.ts | 3 -- tests/baselines/reference/noCatchBlock.js.map | 2 +- .../reference/noCatchBlock.sourcemap.txt | 20 ++++----- .../reference/sourceMap-SkippedNode.js.map | 2 +- .../sourceMap-SkippedNode.sourcemap.txt | 20 ++++----- .../sourceMapValidationStatements.js.map | 2 +- ...ourceMapValidationStatements.sourcemap.txt | 30 ++++++------- .../sourceMapValidationTryCatchFinally.js.map | 2 +- ...MapValidationTryCatchFinally.sourcemap.txt | 42 +++++++++---------- .../getOccurrencesTryCatchFinally.ts | 2 +- 20 files changed, 117 insertions(+), 127 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index dfb5482b09c..01a08a7e43b 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -493,9 +493,7 @@ module ts { break; } case SyntaxKind.Block: - case SyntaxKind.TryBlock: case SyntaxKind.CatchClause: - case SyntaxKind.FinallyBlock: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.SwitchStatement: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7bc8a28a795..e0ff03fa00f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4569,9 +4569,7 @@ module ts { case SyntaxKind.LabeledStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.TryStatement: - case SyntaxKind.TryBlock: case SyntaxKind.CatchClause: - case SyntaxKind.FinallyBlock: return forEachChild(node, isAssignedIn); } return false; @@ -8894,9 +8892,7 @@ module ts { case SyntaxKind.LabeledStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.TryStatement: - case SyntaxKind.TryBlock: case SyntaxKind.CatchClause: - case SyntaxKind.FinallyBlock: case SyntaxKind.VariableDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.EnumDeclaration: diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 24890799706..13766f01996 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -3907,8 +3907,6 @@ module ts { case SyntaxKind.OmittedExpression: return; case SyntaxKind.Block: - case SyntaxKind.TryBlock: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: return emitBlock(node); case SyntaxKind.VariableStatement: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f9ffba74d0c..483090f3538 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -361,8 +361,6 @@ module ts { child((node).whenTrue) || child((node).whenFalse); case SyntaxKind.Block: - case SyntaxKind.TryBlock: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: return children((node).statements); case SyntaxKind.SourceFile: @@ -492,9 +490,7 @@ module ts { case SyntaxKind.DefaultClause: case SyntaxKind.LabeledStatement: case SyntaxKind.TryStatement: - case SyntaxKind.TryBlock: case SyntaxKind.CatchClause: - case SyntaxKind.FinallyBlock: return forEachChild(node, traverse); } } @@ -3529,8 +3525,8 @@ module ts { } // STATEMENTS - function parseBlock(kind: SyntaxKind, ignoreMissingOpenBrace: boolean, checkForStrictMode: boolean, diagnosticMessage?: DiagnosticMessage): Block { - var node = createNode(kind); + function parseBlock(ignoreMissingOpenBrace: boolean, checkForStrictMode: boolean, diagnosticMessage?: DiagnosticMessage): Block { + var node = createNode(SyntaxKind.Block); if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { node.statements = parseList(ParsingContext.BlockStatements, checkForStrictMode, parseStatement); parseExpected(SyntaxKind.CloseBraceToken); @@ -3545,7 +3541,7 @@ module ts { var savedYieldContext = inYieldContext(); setYieldContext(allowYield); - var block = parseBlock(SyntaxKind.Block, ignoreMissingOpenBrace, /*checkForStrictMode*/ true, diagnosticMessage); + var block = parseBlock(ignoreMissingOpenBrace, /*checkForStrictMode*/ true, diagnosticMessage); setYieldContext(savedYieldContext); @@ -3739,25 +3735,19 @@ module ts { // TODO: Review for error recovery function parseTryStatement(): TryStatement { var node = createNode(SyntaxKind.TryStatement); - node.tryBlock = parseTokenAndBlock(SyntaxKind.TryKeyword); + + parseExpected(SyntaxKind.TryKeyword); + node.tryBlock = parseBlock(/*ignoreMissingOpenBrace:*/ false, /*checkForStrictMode*/ false); node.catchClause = token === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined; // If we don't have a catch clause, then we must have a finally clause. Try to parse // one out no matter what. - node.finallyBlock = !node.catchClause || token === SyntaxKind.FinallyKeyword - ? parseTokenAndBlock(SyntaxKind.FinallyKeyword) - : undefined; - return finishNode(node); - } + if (!node.catchClause || token === SyntaxKind.FinallyKeyword) { + parseExpected(SyntaxKind.FinallyKeyword); + node.finallyBlock = parseBlock(/*ignoreMissingOpenBrace:*/ false, /*checkForStrictMode*/ false); + } - function parseTokenAndBlock(token: SyntaxKind): Block { - var pos = getNodePos(); - parseExpected(token); - var result = parseBlock( - token === SyntaxKind.TryKeyword ? SyntaxKind.TryBlock : SyntaxKind.FinallyBlock, - /* ignoreMissingOpenBrace */ false, /*checkForStrictMode*/ false); - result.pos = pos; - return result; + return finishNode(node); } function parseCatchClause(): CatchClause { @@ -3767,7 +3757,7 @@ module ts { result.name = parseIdentifier(); result.type = parseTypeAnnotation(); parseExpected(SyntaxKind.CloseParenToken); - result.block = parseBlock(SyntaxKind.Block, /* ignoreMissingOpenBrace */ false, /*checkForStrictMode*/ false); + result.block = parseBlock(/*ignoreMissingOpenBrace:*/ false, /*checkForStrictMode:*/ false); return finishNode(result); } @@ -3876,7 +3866,7 @@ module ts { function parseStatement(): Statement { switch (token) { case SyntaxKind.OpenBraceToken: - return parseBlock(SyntaxKind.Block, /* ignoreMissingOpenBrace */ false, /*checkForStrictMode*/ false); + return parseBlock(/*ignoreMissingOpenBrace:*/ false, /*checkForStrictMode:*/ false); case SyntaxKind.VarKeyword: case SyntaxKind.ConstKeyword: // const here should always be parsed as const declaration because of check in 'isStatement' @@ -4715,7 +4705,7 @@ module ts { inFunctionBlock = true; } var savedInBlock = inBlock; - if (node.kind === SyntaxKind.Block || node.kind === SyntaxKind.TryBlock || node.kind === SyntaxKind.FinallyBlock) { + if (node.kind === SyntaxKind.Block) { inBlock = true; } var savedInObjectLiteralExpression = inObjectLiteralExpression; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ab4571bfb9a..43680e235ff 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -213,8 +213,6 @@ module ts { LabeledStatement, ThrowStatement, TryStatement, - TryBlock, - FinallyBlock, DebuggerStatement, VariableDeclaration, FunctionDeclaration, diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 3a2cc2ad7e7..8c3dda08d22 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -108,8 +108,6 @@ module ts.BreakpointResolver { return spanInFunctionBlock(node); } // Fall through - case SyntaxKind.TryBlock: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: return spanInBlock(node); @@ -429,9 +427,7 @@ module ts.BreakpointResolver { } // fall through. - case SyntaxKind.TryBlock: case SyntaxKind.CatchClause: - case SyntaxKind.FinallyBlock: return spanInNode((node.parent).statements[(node.parent).statements.length - 1]);; case SyntaxKind.SwitchStatement: diff --git a/src/services/formatting.ts b/src/services/formatting.ts index 0c2dd4c51dc..c8d3d1bb52f 100644 --- a/src/services/formatting.ts +++ b/src/services/formatting.ts @@ -154,8 +154,6 @@ module ts.formatting { return body && body.kind === SyntaxKind.Block && rangeContainsRange((body).statements, node); case SyntaxKind.SourceFile: case SyntaxKind.Block: - case SyntaxKind.TryBlock: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: return rangeContainsRange((parent).statements, node); case SyntaxKind.CatchClause: @@ -932,9 +930,6 @@ module ts.formatting { function isSomeBlock(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.Block: - case SyntaxKind.Block: - case SyntaxKind.TryBlock: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: return true; } diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index bea74e529ff..9fcc787030d 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -525,8 +525,6 @@ module ts.formatting { case SyntaxKind.Block: case SyntaxKind.SwitchStatement: case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TryBlock: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: return true; } @@ -580,9 +578,7 @@ module ts.formatting { case SyntaxKind.ModuleDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.Block: - case SyntaxKind.TryBlock: case SyntaxKind.CatchClause: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: case SyntaxKind.SwitchStatement: return true; @@ -603,7 +599,6 @@ module ts.formatting { // TODO // case SyntaxKind.ElseClause: case SyntaxKind.CatchClause: - case SyntaxKind.FinallyBlock: return true; default: diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 83eef2f4378..649266f1f88 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -84,25 +84,43 @@ module ts { parent.kind === SyntaxKind.CatchClause) { addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n)); + break; } - else { - // Block was a standalone block. In this case we want to only collapse - // the span of the block, independent of any parent span. - var span = TextSpan.fromBounds(n.getStart(), n.end); - elements.push({ - textSpan: span, - hintSpan: span, - bannerText: collapseText, - autoCollapse: autoCollapse(n) - }); + + if (parent.kind === SyntaxKind.TryStatement) { + // Could be the try-block, or the finally-block. + var tryStatement = parent; + if (tryStatement.tryBlock === n) { + addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n)); + break; + } + else if (tryStatement.finallyBlock === n) { + var children = tryStatement.getChildren(); + for (var i = 0, m = children.length; i < m; i++) { + if (children[i].kind === SyntaxKind.FinallyKeyword) { + addOutliningSpan(children[i], openBrace, closeBrace, autoCollapse(n)); + break; + } + } + } + + // fall through. } + + // Block was a standalone block. In this case we want to only collapse + // the span of the block, independent of any parent span. + var span = TextSpan.fromBounds(n.getStart(), n.end); + elements.push({ + textSpan: span, + hintSpan: span, + bannerText: collapseText, + autoCollapse: autoCollapse(n) + }); break; } // Fallthrough. case SyntaxKind.ModuleBlock: - case SyntaxKind.TryBlock: - case SyntaxKind.FinallyBlock: var openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile); var closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile); addOutliningSpan(n.parent, openBrace, closeBrace, autoCollapse(n)); diff --git a/src/services/services.ts b/src/services/services.ts index 74e7f924a20..ccf29d600b6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3418,13 +3418,17 @@ module ts { return getThrowOccurrences(node.parent); } break; - case SyntaxKind.TryKeyword: case SyntaxKind.CatchKeyword: - case SyntaxKind.FinallyKeyword: if (hasKind(parent(parent(node)), SyntaxKind.TryStatement)) { return getTryCatchFinallyOccurrences(node.parent.parent); } break; + case SyntaxKind.TryKeyword: + case SyntaxKind.FinallyKeyword: + if (hasKind(parent(node), SyntaxKind.TryStatement)) { + return getTryCatchFinallyOccurrences(node.parent); + } + break; case SyntaxKind.SwitchKeyword: if (hasKind(node.parent, SyntaxKind.SwitchStatement)) { return getSwitchCaseDefaultOccurrences(node.parent); @@ -3658,7 +3662,12 @@ module ts { } if (tryStatement.finallyBlock) { - pushKeywordIf(keywords, tryStatement.finallyBlock.getFirstToken(), SyntaxKind.FinallyKeyword); + var children = tryStatement.getChildren(); + for (var i = 0, n = children.length; i < n; i++) { + if (pushKeywordIf(keywords, children[i], SyntaxKind.FinallyKeyword)) { + break; + } + } } return map(keywords, getReferenceEntryFromNode); diff --git a/src/services/smartIndenter.ts b/src/services/smartIndenter.ts index cafd92a7551..c6b92310c2e 100644 --- a/src/services/smartIndenter.ts +++ b/src/services/smartIndenter.ts @@ -327,8 +327,6 @@ module ts.formatting { case SyntaxKind.EnumDeclaration: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.Block: - case SyntaxKind.TryBlock: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.TypeLiteral: @@ -403,7 +401,6 @@ module ts.formatting { case SyntaxKind.EnumDeclaration: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.Block: - case SyntaxKind.FinallyBlock: case SyntaxKind.ModuleBlock: case SyntaxKind.SwitchStatement: return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); diff --git a/tests/baselines/reference/noCatchBlock.js.map b/tests/baselines/reference/noCatchBlock.js.map index dccc7798c92..149a2167f46 100644 --- a/tests/baselines/reference/noCatchBlock.js.map +++ b/tests/baselines/reference/noCatchBlock.js.map @@ -1,2 +1,2 @@ //// [noCatchBlock.js.map] -{"version":3,"file":"noCatchBlock.js","sourceRoot":"","sources":["noCatchBlock.ts"],"names":[],"mappings":"AACA,IAAA,CAAC;AAED,CAAC;QAAC,CAAC;AAEH,CAAC"} \ No newline at end of file +{"version":3,"file":"noCatchBlock.js","sourceRoot":"","sources":["noCatchBlock.ts"],"names":[],"mappings":"AACA,IAAI,CAAC;AAEL,CAAC;QAAS,CAAC;AAEX,CAAC"} \ No newline at end of file diff --git a/tests/baselines/reference/noCatchBlock.sourcemap.txt b/tests/baselines/reference/noCatchBlock.sourcemap.txt index 8d5a67339f6..18cb0d5a479 100644 --- a/tests/baselines/reference/noCatchBlock.sourcemap.txt +++ b/tests/baselines/reference/noCatchBlock.sourcemap.txt @@ -14,17 +14,17 @@ sourceFile:noCatchBlock.ts 3 > ^ 1 > > -2 > -3 > t +2 >try +3 > { 1 >Emitted(1, 1) Source(2, 1) + SourceIndex(0) -2 >Emitted(1, 5) Source(2, 1) + SourceIndex(0) -3 >Emitted(1, 6) Source(2, 2) + SourceIndex(0) +2 >Emitted(1, 5) Source(2, 5) + SourceIndex(0) +3 >Emitted(1, 6) Source(2, 6) + SourceIndex(0) --- >>>} 1 > 2 >^ 3 > ^^^^^^^^^-> -1 >ry { +1 > > // ... > 2 >} @@ -34,16 +34,16 @@ sourceFile:noCatchBlock.ts >>>finally { 1->^^^^^^^^ 2 > ^ -1-> -2 > f -1->Emitted(3, 9) Source(4, 3) + SourceIndex(0) -2 >Emitted(3, 10) Source(4, 4) + SourceIndex(0) +1-> finally +2 > { +1->Emitted(3, 9) Source(4, 11) + SourceIndex(0) +2 >Emitted(3, 10) Source(4, 12) + SourceIndex(0) --- >>>} 1 > 2 >^ 3 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> -1 >inally { +1 > > // N.B. No 'catch' block > 2 >} diff --git a/tests/baselines/reference/sourceMap-SkippedNode.js.map b/tests/baselines/reference/sourceMap-SkippedNode.js.map index cdcdc804634..77340424640 100644 --- a/tests/baselines/reference/sourceMap-SkippedNode.js.map +++ b/tests/baselines/reference/sourceMap-SkippedNode.js.map @@ -1,2 +1,2 @@ //// [sourceMap-SkippedNode.js.map] -{"version":3,"file":"sourceMap-SkippedNode.js","sourceRoot":"","sources":["sourceMap-SkippedNode.ts"],"names":[],"mappings":"AAAA,IAAA,CAAC;AAED,CAAC;QAAC,CAAC;AAEH,CAAC"} \ No newline at end of file +{"version":3,"file":"sourceMap-SkippedNode.js","sourceRoot":"","sources":["sourceMap-SkippedNode.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC;AAEL,CAAC;QAAS,CAAC;AAEX,CAAC"} \ No newline at end of file diff --git a/tests/baselines/reference/sourceMap-SkippedNode.sourcemap.txt b/tests/baselines/reference/sourceMap-SkippedNode.sourcemap.txt index 531a666ade0..f68320a531f 100644 --- a/tests/baselines/reference/sourceMap-SkippedNode.sourcemap.txt +++ b/tests/baselines/reference/sourceMap-SkippedNode.sourcemap.txt @@ -13,17 +13,17 @@ sourceFile:sourceMap-SkippedNode.ts 2 >^^^^ 3 > ^ 1 > -2 > -3 > t +2 >try +3 > { 1 >Emitted(1, 1) Source(1, 1) + SourceIndex(0) -2 >Emitted(1, 5) Source(1, 1) + SourceIndex(0) -3 >Emitted(1, 6) Source(1, 2) + SourceIndex(0) +2 >Emitted(1, 5) Source(1, 5) + SourceIndex(0) +3 >Emitted(1, 6) Source(1, 6) + SourceIndex(0) --- >>>} 1 > 2 >^ 3 > ^^^^^^^^^-> -1 >ry { +1 > >// ... > 2 >} @@ -33,16 +33,16 @@ sourceFile:sourceMap-SkippedNode.ts >>>finally { 1->^^^^^^^^ 2 > ^ -1-> -2 > f -1->Emitted(3, 9) Source(3, 3) + SourceIndex(0) -2 >Emitted(3, 10) Source(3, 4) + SourceIndex(0) +1-> finally +2 > { +1->Emitted(3, 9) Source(3, 11) + SourceIndex(0) +2 >Emitted(3, 10) Source(3, 12) + SourceIndex(0) --- >>>} 1 > 2 >^ 3 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> -1 >inally { +1 > >// N.B. No 'catch' block > 2 >} diff --git a/tests/baselines/reference/sourceMapValidationStatements.js.map b/tests/baselines/reference/sourceMapValidationStatements.js.map index 8eb99944914..7bf883de6c3 100644 --- a/tests/baselines/reference/sourceMapValidationStatements.js.map +++ b/tests/baselines/reference/sourceMapValidationStatements.js.map @@ -1,2 +1,2 @@ //// [sourceMapValidationStatements.js.map] -{"version":3,"file":"sourceMapValidationStatements.js","sourceRoot":"","sources":["sourceMapValidationStatements.ts"],"names":["f"],"mappings":"AAAA,SAAS,CAAC;IACNA,IAAIA,CAACA,CAACA;IACNA,IAAIA,CAACA,GAAGA,CAACA,CAACA;IACVA,GAAGA,CAACA,CAACA,GAAGA,CAACA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,EAAEA,EAAEA,CAACA,EAAEA,EAAEA,CAACA;QAC1BA,CAACA,IAAIA,CAACA,CAACA;QACPA,CAACA,IAAIA,CAACA,CAACA;IACXA,CAACA;IACDA,EAAEA,CAACA,CAACA,CAACA,GAAGA,EAAEA,CAACA,CAACA,CAACA;QACTA,CAACA,IAAIA,CAACA,CAACA;IACXA,CAACA;IAACA,IAAIA,CAACA,CAACA;QACJA,CAACA,IAAIA,EAAEA,CAACA;QACRA,CAACA,EAAEA,CAACA;IACRA,CAACA;IACDA,IAAIA,CAACA,GAAGA;QACJA,CAACA;QACDA,CAACA;QACDA,CAACA;KACJA,CAACA;IACFA,IAAIA,GAAGA,GAAGA;QACNA,CAACA,EAAEA,CAACA;QACJA,CAACA,EAAEA,OAAOA;KACbA,CAACA;IACFA,GAAGA,CAACA,CAACA,GAAGA,CAACA,CAACA,IAAIA,CAACA,CAACA,CAACA,CAACA;QACdA,GAAGA,CAACA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA,CAACA;QACbA,IAAIA,CAACA,GAAGA,EAAEA,CAACA;IACfA,CAACA;IACDA,IAAAA,CAACA;QACGA,GAAGA,CAACA,CAACA,GAAGA,MAAMA,CAACA;IACnBA,CAAEA;IAAAA,KAAKA,CAACA,CAACA,CAACA,CAACA,CAACA,CAACA;QACTA,EAAEA,CAACA,CAACA,GAAGA,CAACA,CAACA,GAAGA,EAAEA,CAACA,CAACA,CAACA;YACbA,GAAGA,CAACA,CAACA,GAAGA,EAAEA,CAACA;QACfA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACJA,GAAGA,CAACA,CAACA,GAAGA,KAAKA,CAACA;QAClBA,CAACA;IACLA,CAACA;IACDA,IAAAA,CAACA;QACGA,MAAMA,IAAIA,KAAKA,EAAEA,CAACA;IACtBA,CAAEA;IAAAA,KAAKA,CAACA,CAACA,EAAEA,CAACA,CAACA,CAACA;QACVA,IAAIA,CAACA,GAAGA,EAAEA,CAACA;IACfA,CAACA;YAACA,CAACA;QACCA,CAACA,GAAGA,EAAEA,CAACA;IACXA,CAACA;IACDA,MAAMA,GAAGA,EAAEA,CAACA;QACRA,CAACA,GAAGA,CAACA,CAACA;QACNA,CAACA,GAAGA,EAAEA,CAACA;IACXA,CAACA;IACDA,MAAMA,CAACA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA,CAACA;QACZA,KAAKA,CAACA,EAAEA,CAACA;YACLA,CAACA,EAAEA,CAACA;YACJA,KAAKA,CAACA;QAEVA,CAACA;QACDA,KAAKA,CAACA,EAAEA,CAACA;YACLA,CAACA,EAAEA,CAACA;YACJA,KAAKA,CAACA;QAEVA,CAACA;QACDA,SAASA,CAACA;YACNA,CAACA,IAAIA,CAACA,CAACA;YACPA,CAACA,GAAGA,EAAEA,CAACA;YACPA,KAAKA,CAACA;QAEVA,CAACA;IACLA,CAACA;IACDA,OAAOA,CAACA,GAAGA,EAAEA,EAAEA,CAACA;QACZA,CAACA,EAAEA,CAACA;IACRA,CAACA;IACDA,GAAGA,CAACA;QACAA,CAACA,EAAEA,CAACA;IACRA,CAACA,QAAQA,CAACA,GAAGA,CAACA,EAACA;IACfA,CAACA,GAAGA,CAACA,CAACA;IACNA,IAAIA,CAACA,GAAGA,CAACA,CAACA,IAAIA,CAACA,CAACA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,CAACA,CAACA;IACjCA,CAACA,CAACA,IAAIA,CAACA,CAACA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,CAACA,CAACA;IACzBA,CAACA,KAAKA,CAACA,CAACA;IACRA,CAACA,GAAGA,CAACA,GAAGA,EAAEA,CAACA;IACXA,IAAIA,CAACA,GAAGA,CAACA,CAACA;IACVA,MAAMA,CAACA;AACXA,CAACA;AACD,IAAI,CAAC,GAAG;IACJ,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACd,CAAC,CAAC;AACF,CAAC,EAAE,CAAC"} \ No newline at end of file +{"version":3,"file":"sourceMapValidationStatements.js","sourceRoot":"","sources":["sourceMapValidationStatements.ts"],"names":["f"],"mappings":"AAAA,SAAS,CAAC;IACNA,IAAIA,CAACA,CAACA;IACNA,IAAIA,CAACA,GAAGA,CAACA,CAACA;IACVA,GAAGA,CAACA,CAACA,GAAGA,CAACA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,EAAEA,EAAEA,CAACA,EAAEA,EAAEA,CAACA;QAC1BA,CAACA,IAAIA,CAACA,CAACA;QACPA,CAACA,IAAIA,CAACA,CAACA;IACXA,CAACA;IACDA,EAAEA,CAACA,CAACA,CAACA,GAAGA,EAAEA,CAACA,CAACA,CAACA;QACTA,CAACA,IAAIA,CAACA,CAACA;IACXA,CAACA;IAACA,IAAIA,CAACA,CAACA;QACJA,CAACA,IAAIA,EAAEA,CAACA;QACRA,CAACA,EAAEA,CAACA;IACRA,CAACA;IACDA,IAAIA,CAACA,GAAGA;QACJA,CAACA;QACDA,CAACA;QACDA,CAACA;KACJA,CAACA;IACFA,IAAIA,GAAGA,GAAGA;QACNA,CAACA,EAAEA,CAACA;QACJA,CAACA,EAAEA,OAAOA;KACbA,CAACA;IACFA,GAAGA,CAACA,CAACA,GAAGA,CAACA,CAACA,IAAIA,CAACA,CAACA,CAACA,CAACA;QACdA,GAAGA,CAACA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA,CAACA;QACbA,IAAIA,CAACA,GAAGA,EAAEA,CAACA;IACfA,CAACA;IACDA,IAAIA,CAACA;QACDA,GAAGA,CAACA,CAACA,GAAGA,MAAMA,CAACA;IACnBA,CAAEA;IAAAA,KAAKA,CAACA,CAACA,CAACA,CAACA,CAACA,CAACA;QACTA,EAAEA,CAACA,CAACA,GAAGA,CAACA,CAACA,GAAGA,EAAEA,CAACA,CAACA,CAACA;YACbA,GAAGA,CAACA,CAACA,GAAGA,EAAEA,CAACA;QACfA,CAACA;QAACA,IAAIA,CAACA,CAACA;YACJA,GAAGA,CAACA,CAACA,GAAGA,KAAKA,CAACA;QAClBA,CAACA;IACLA,CAACA;IACDA,IAAIA,CAACA;QACDA,MAAMA,IAAIA,KAAKA,EAAEA,CAACA;IACtBA,CAAEA;IAAAA,KAAKA,CAACA,CAACA,EAAEA,CAACA,CAACA,CAACA;QACVA,IAAIA,CAACA,GAAGA,EAAEA,CAACA;IACfA,CAACA;YAASA,CAACA;QACPA,CAACA,GAAGA,EAAEA,CAACA;IACXA,CAACA;IACDA,MAAMA,GAAGA,EAAEA,CAACA;QACRA,CAACA,GAAGA,CAACA,CAACA;QACNA,CAACA,GAAGA,EAAEA,CAACA;IACXA,CAACA;IACDA,MAAMA,CAACA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA,CAACA;QACZA,KAAKA,CAACA,EAAEA,CAACA;YACLA,CAACA,EAAEA,CAACA;YACJA,KAAKA,CAACA;QAEVA,CAACA;QACDA,KAAKA,CAACA,EAAEA,CAACA;YACLA,CAACA,EAAEA,CAACA;YACJA,KAAKA,CAACA;QAEVA,CAACA;QACDA,SAASA,CAACA;YACNA,CAACA,IAAIA,CAACA,CAACA;YACPA,CAACA,GAAGA,EAAEA,CAACA;YACPA,KAAKA,CAACA;QAEVA,CAACA;IACLA,CAACA;IACDA,OAAOA,CAACA,GAAGA,EAAEA,EAAEA,CAACA;QACZA,CAACA,EAAEA,CAACA;IACRA,CAACA;IACDA,GAAGA,CAACA;QACAA,CAACA,EAAEA,CAACA;IACRA,CAACA,QAAQA,CAACA,GAAGA,CAACA,EAACA;IACfA,CAACA,GAAGA,CAACA,CAACA;IACNA,IAAIA,CAACA,GAAGA,CAACA,CAACA,IAAIA,CAACA,CAACA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,CAACA,CAACA;IACjCA,CAACA,CAACA,IAAIA,CAACA,CAACA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,CAACA,CAACA;IACzBA,CAACA,KAAKA,CAACA,CAACA;IACRA,CAACA,GAAGA,CAACA,GAAGA,EAAEA,CAACA;IACXA,IAAIA,CAACA,GAAGA,CAACA,CAACA;IACVA,MAAMA,CAACA;AACXA,CAACA;AACD,IAAI,CAAC,GAAG;IACJ,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACd,CAAC,CAAC;AACF,CAAC,EAAE,CAAC"} \ No newline at end of file diff --git a/tests/baselines/reference/sourceMapValidationStatements.sourcemap.txt b/tests/baselines/reference/sourceMapValidationStatements.sourcemap.txt index ef7aef275c0..586063c7c12 100644 --- a/tests/baselines/reference/sourceMapValidationStatements.sourcemap.txt +++ b/tests/baselines/reference/sourceMapValidationStatements.sourcemap.txt @@ -501,11 +501,11 @@ sourceFile:sourceMapValidationStatements.ts 4 > ^^^^^^^^^^^^^^^-> 1-> > -2 > -3 > t +2 > try +3 > { 1->Emitted(28, 5) Source(27, 5) + SourceIndex(0) name (f) -2 >Emitted(28, 9) Source(27, 5) + SourceIndex(0) name (f) -3 >Emitted(28, 10) Source(27, 6) + SourceIndex(0) name (f) +2 >Emitted(28, 9) Source(27, 9) + SourceIndex(0) name (f) +3 >Emitted(28, 10) Source(27, 10) + SourceIndex(0) name (f) --- >>> obj.q = "ohhh"; 1->^^^^^^^^ @@ -515,7 +515,7 @@ sourceFile:sourceMapValidationStatements.ts 5 > ^^^ 6 > ^^^^^^ 7 > ^ -1->ry { +1-> > 2 > obj 3 > . @@ -706,11 +706,11 @@ sourceFile:sourceMapValidationStatements.ts 4 > ^^^^^^^^^^^^^^^^^^-> 1-> > -2 > -3 > t +2 > try +3 > { 1->Emitted(39, 5) Source(36, 5) + SourceIndex(0) name (f) -2 >Emitted(39, 9) Source(36, 5) + SourceIndex(0) name (f) -3 >Emitted(39, 10) Source(36, 6) + SourceIndex(0) name (f) +2 >Emitted(39, 9) Source(36, 9) + SourceIndex(0) name (f) +3 >Emitted(39, 10) Source(36, 10) + SourceIndex(0) name (f) --- >>> throw new Error(); 1->^^^^^^^^ @@ -719,7 +719,7 @@ sourceFile:sourceMapValidationStatements.ts 4 > ^^^^^ 5 > ^^ 6 > ^ -1->ry { +1-> > 2 > throw 3 > new @@ -805,10 +805,10 @@ sourceFile:sourceMapValidationStatements.ts 1->^^^^^^^^^^^^ 2 > ^ 3 > ^^^-> -1-> -2 > f -1->Emitted(45, 13) Source(40, 7) + SourceIndex(0) name (f) -2 >Emitted(45, 14) Source(40, 8) + SourceIndex(0) name (f) +1-> finally +2 > { +1->Emitted(45, 13) Source(40, 15) + SourceIndex(0) name (f) +2 >Emitted(45, 14) Source(40, 16) + SourceIndex(0) name (f) --- >>> y = 70; 1->^^^^^^^^ @@ -816,7 +816,7 @@ sourceFile:sourceMapValidationStatements.ts 3 > ^^^ 4 > ^^ 5 > ^ -1->inally { +1-> > 2 > y 3 > = diff --git a/tests/baselines/reference/sourceMapValidationTryCatchFinally.js.map b/tests/baselines/reference/sourceMapValidationTryCatchFinally.js.map index 89716807538..46506410856 100644 --- a/tests/baselines/reference/sourceMapValidationTryCatchFinally.js.map +++ b/tests/baselines/reference/sourceMapValidationTryCatchFinally.js.map @@ -1,2 +1,2 @@ //// [sourceMapValidationTryCatchFinally.js.map] -{"version":3,"file":"sourceMapValidationTryCatchFinally.js","sourceRoot":"","sources":["sourceMapValidationTryCatchFinally.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,EAAE,CAAC;AACX,IAAA,CAAC;IACG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACd,CAAE;AAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACT,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;QAAC,CAAC;IACC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;AACf,CAAC;AACD,IAAA,CAAC;IAEG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,IAAI,KAAK,EAAE,CAAC;AACtB,CACA;AAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CACT,CAAC;IACG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;QACD,CAAC;IAEG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;AACf,CAAC"} \ No newline at end of file +{"version":3,"file":"sourceMapValidationTryCatchFinally.js","sourceRoot":"","sources":["sourceMapValidationTryCatchFinally.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,EAAE,CAAC;AACX,IAAI,CAAC;IACD,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACd,CAAE;AAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACT,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;QAAS,CAAC;IACP,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;AACf,CAAC;AACD,IACA,CAAC;IACG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,IAAI,KAAK,EAAE,CAAC;AACtB,CACA;AAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CACT,CAAC;IACG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;QAED,CAAC;IACG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;AACf,CAAC"} \ No newline at end of file diff --git a/tests/baselines/reference/sourceMapValidationTryCatchFinally.sourcemap.txt b/tests/baselines/reference/sourceMapValidationTryCatchFinally.sourcemap.txt index 25dee4fbc45..d625e74c262 100644 --- a/tests/baselines/reference/sourceMapValidationTryCatchFinally.sourcemap.txt +++ b/tests/baselines/reference/sourceMapValidationTryCatchFinally.sourcemap.txt @@ -35,11 +35,11 @@ sourceFile:sourceMapValidationTryCatchFinally.ts 4 > ^^^^^^^^^^-> 1 > > -2 > -3 > t +2 >try +3 > { 1 >Emitted(2, 1) Source(2, 1) + SourceIndex(0) -2 >Emitted(2, 5) Source(2, 1) + SourceIndex(0) -3 >Emitted(2, 6) Source(2, 2) + SourceIndex(0) +2 >Emitted(2, 5) Source(2, 5) + SourceIndex(0) +3 >Emitted(2, 6) Source(2, 6) + SourceIndex(0) --- >>> x = x + 1; 1->^^^^ @@ -49,7 +49,7 @@ sourceFile:sourceMapValidationTryCatchFinally.ts 5 > ^^^ 6 > ^ 7 > ^ -1->ry { +1-> > 2 > x 3 > = @@ -140,10 +140,10 @@ sourceFile:sourceMapValidationTryCatchFinally.ts 1->^^^^^^^^ 2 > ^ 3 > ^^^^^^^-> -1-> -2 > f -1->Emitted(8, 9) Source(6, 3) + SourceIndex(0) -2 >Emitted(8, 10) Source(6, 4) + SourceIndex(0) +1-> finally +2 > { +1->Emitted(8, 9) Source(6, 11) + SourceIndex(0) +2 >Emitted(8, 10) Source(6, 12) + SourceIndex(0) --- >>> x = x * 10; 1->^^^^ @@ -153,7 +153,7 @@ sourceFile:sourceMapValidationTryCatchFinally.ts 5 > ^^^ 6 > ^^ 7 > ^ -1->inally { +1-> > 2 > x 3 > = @@ -186,11 +186,12 @@ sourceFile:sourceMapValidationTryCatchFinally.ts 4 > ^^^^^^^^^^-> 1-> > -2 > -3 > t +2 >try + > +3 > { 1->Emitted(11, 1) Source(9, 1) + SourceIndex(0) -2 >Emitted(11, 5) Source(9, 1) + SourceIndex(0) -3 >Emitted(11, 6) Source(9, 2) + SourceIndex(0) +2 >Emitted(11, 5) Source(10, 1) + SourceIndex(0) +3 >Emitted(11, 6) Source(10, 2) + SourceIndex(0) --- >>> x = x + 1; 1->^^^^ @@ -201,8 +202,7 @@ sourceFile:sourceMapValidationTryCatchFinally.ts 6 > ^ 7 > ^ 8 > ^^^^^^^^^-> -1->ry - >{ +1-> > 2 > x 3 > = @@ -317,10 +317,11 @@ sourceFile:sourceMapValidationTryCatchFinally.ts 2 > ^ 3 > ^^^^^^^-> 1-> + >finally > -2 > f -1->Emitted(18, 9) Source(18, 1) + SourceIndex(0) -2 >Emitted(18, 10) Source(18, 2) + SourceIndex(0) +2 > { +1->Emitted(18, 9) Source(19, 1) + SourceIndex(0) +2 >Emitted(18, 10) Source(19, 2) + SourceIndex(0) --- >>> x = x * 10; 1->^^^^ @@ -330,8 +331,7 @@ sourceFile:sourceMapValidationTryCatchFinally.ts 5 > ^^^ 6 > ^^ 7 > ^ -1->inally - >{ +1-> > 2 > x 3 > = diff --git a/tests/cases/fourslash/getOccurrencesTryCatchFinally.ts b/tests/cases/fourslash/getOccurrencesTryCatchFinally.ts index 9037b6f8029..db0f792880e 100644 --- a/tests/cases/fourslash/getOccurrencesTryCatchFinally.ts +++ b/tests/cases/fourslash/getOccurrencesTryCatchFinally.ts @@ -16,7 +16,7 @@ ////[|fina/*3*/lly|] { ////} - +debugger; for (var i = 1; i <= test.markers().length; i++) { goTo.marker("" + i); verify.occurrencesAtPositionCount(3); From 400cf91e9698a677e2e05492a4b2366732408ffe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Dec 2014 18:23:14 -0800 Subject: [PATCH 18/50] Nodes are not resuable if the parser has a outstanding, unattached, parse error. This is conservative, but safe. If we wanted to support node reuse here, we'd have to carefully ensure that the errors and tree shape would be the same that hte normal parse would produce. --- src/compiler/parser.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ba05c01d199..7d098d315e3 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1436,6 +1436,17 @@ module ts { } function currentNode(parsingContext: ParsingContext): Node { + // If there is an outstanding parse error that we've encountered, but not attached to + // some node, then we cannot get a node from the old source tree. This is because we + // want to mark the next node we encounter as being unusable. + // + // Note: This may be too conservative. Perhaps we could reuse hte node and set the bit + // on it (or its leftmost child) as having the error. For now though, being conservative + // is nice and likely won't ever affect perf. + if (parseErrorBeforeNextFinishedNode) { + return undefined; + } + var node: Node = currentNodeFromCursor(); if (!node) { return undefined; From c9f8aaecb6221992891afa427ce2a67d83d8d349 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Dec 2014 22:16:06 -0800 Subject: [PATCH 19/50] Don't incrementally parse when teh old tree had no source module elements. Also, provide explanatory comments as to why we pass setNodeParents:true. --- src/compiler/parser.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 7d098d315e3..dbb3f365e2f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -597,6 +597,12 @@ module ts { return sourceFile; } + if (sourceFile.statements.length === 0) { + // If we don't have any statements in the current source file, hten there's no real + // way to incrementally parse. So just do a full parse instead. + return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); + } + // Make the actual change larger so that we know to reparse anything whose lookahead // might have intersected the change. var changeRange = extendToAffectedRange(textChangeRange); @@ -625,6 +631,13 @@ module ts { // Don't pass along the text change range for now. We'll pass it along once incremental // parsing is enabled. + // + // Note: passing in 'true' for setNodeParents is very important. When incrementally + // parsing, we will be reusing nodes from the old tree, and placing it into new + // parents. If we don't set the parents now, we'll end up with an observably + // inconsistent tree. Setting the parents on the new tree should be very fast. We + // will immediately bail out of walking any subtrees when we can see that their parents + // are already correct. return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); } From 14cb05f443b20801f1119ebfdef7a2a55e240287 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Dec 2014 23:39:44 -0800 Subject: [PATCH 20/50] Add explanatory comments to explain how node moving works. --- src/compiler/parser.ts | 98 +++++++++++++++++++++++++++++++++++++++--- src/compiler/types.ts | 2 +- 2 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index dbb3f365e2f..f15de1a2ea5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -437,6 +437,7 @@ module ts { } interface IncrementalElement extends TextRange { + parent?: Node; intersectsChange: boolean length?: number; _children: Node[]; @@ -624,10 +625,15 @@ module ts { // is ahead of us, then we'll need to rescan tokens. If the node's position is behind // us, then we'll need to skip it or crumble it as appropriate // + // We will also adjust the positions of nodes that intersect the change range as well. + // By doing this, we ensure that all the positions in the old tree are consistent, not + // just the positions of nodes entirely before/after the change range. By being + // consistent, we can then easily map from positions to nodes in the old tree easily. + // // Also, mark any syntax elements that intersect the changed span. We know, up front, // that we cannot reuse these elements. updateTokenPositionsAndMarkElements(sourceFile, - changeRange.span().start(), changeRange.span().end(), delta); + changeRange.span().start(), changeRange.span().end(), changeRange.newSpan().end(), delta); // Don't pass along the text change range for now. We'll pass it along once incremental // parsing is enabled. @@ -641,12 +647,14 @@ module ts { return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); } - function updateTokenPositionsAndMarkElements(node: IncrementalNode, changeStart: number, changeRangeOldEnd: number, delta: number): void { + function updateTokenPositionsAndMarkElements(node: IncrementalNode, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number): void { forEachChild(node, visitNode, visitArray); function visitNode(child: IncrementalNode) { if (child.pos > changeRangeOldEnd) { - forceUpdateTokenPositionsForElement(child, delta); + // Node is entirely past the change range. We need to move both its pos and + // end, forward or backward appropriately. + moveElementEntirelyPastChangeRange(child, delta); } else { // Check if the element intersects the change range. If it does, then it is not @@ -655,6 +663,9 @@ module ts { var fullEnd = child.end; if (fullEnd >= changeStart) { child.intersectsChange = true; + + // Adjust the pos or end (or both) of the intersecting element accordingly. + adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); forEachChild(child, visitNode, visitArray); } // else { @@ -667,7 +678,7 @@ module ts { if (array.pos > changeRangeOldEnd) { // Array is entirely after the change range. We need to move it, and move any of // its children. - forceUpdateTokenPositionsForElement(array, delta); + moveElementEntirelyPastChangeRange(array, delta); } else { // Check if the element intersects the change range. If it does, then it is not @@ -676,6 +687,9 @@ module ts { var fullEnd = array.end; if (fullEnd >= changeStart) { array.intersectsChange = true; + + // Adjust the pos or end (or both) of the intersecting array accordingly. + adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); for (var i = 0, n = array.length; i < n; i++) { forEachChild(array[i], visitNode, visitArray); } @@ -687,7 +701,81 @@ module ts { } } - function forceUpdateTokenPositionsForElement(element: IncrementalElement, delta: number) { + function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { + Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range"); + Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range"); + + // We have an element that intersects the change range in some way. It may have its + // start, or its end (or both) in the changed range. We want to adjust any part + // that intersects such that the final tree is in a consistent state. i.e. all + // chlidren have spans within the span of their parent, and all siblings are ordered + // properly. + + // We may need to update both the 'pos' and the 'end' of the element. + + // If the 'pos' is before the start of the change, then we don't need to touch it. + // If it isn't, then the 'pos' must be inside the change. How we update it will + // depend if delta is positive or negative. If delta is positive then we have + // something like: + // + // -------------------AAA----------------- + // -------------------BBBCCCCCCC----------------- + // + // In this case, we consider any node that started in the change range to still be + // starting at the same position. + // + // however, if the delta is negative, then we instead have something like this: + // + // -------------------XXXYYYYYYY----------------- + // -------------------ZZZ----------------- + // + // In this case, any element that started in the 'X' range will keep its position. + // However any element htat started after that will have their pos adjusted to be + // at the end of the new range. i.e. any node that started in the 'Y' range will + // be adjusted to have their start at the end of the 'Z' range. + // + // The element will keep its position if possible. Or Move backward to the new-end + // if it's in the 'Y' range. + element.pos = Math.min(element.pos, changeRangeNewEnd); + + // If the 'end' is after the change range, then we always adjust it by the delta + // amount. However, if the end is in the change range, then how we adjust it + // will depend on if delta is positive or negative. If delta is positive then we + // have something like: + // + // -------------------AAA----------------- + // -------------------BBBCCCCCCC----------------- + // + // In this case, we consider any node that ended inside the change range to keep its + // end position. + // + // however, if the delta is negative, then we instead have something like this: + // + // -------------------XXXYYYYYYY----------------- + // -------------------ZZZ----------------- + // + // In this case, any element that ended in the 'X' range will keep its position. + // However any element htat ended after that will have their pos adjusted to be + // at the end of the new range. i.e. any node that ended in the 'Y' range will + // be adjusted to have their end at the end of the 'Z' range. + if (element.end >= changeRangeOldEnd) { + // Element ends after the change range. Always adjust the end pos. + element.end += delta; + } + else { + // Element ends in the change range. The element will keep its position if + // possible. Or Move backward to the new-end if it's in the 'Y' range. + element.end = Math.min(element.end, changeRangeNewEnd); + } + + Debug.assert(element.pos <= element.end); + if (element.parent) { + Debug.assert(element.pos >= element.parent.pos); + Debug.assert(element.end <= element.parent.end); + } + } + + function moveElementEntirelyPastChangeRange(element: IncrementalElement, delta: number) { if (element.length) { visitArray(element); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 381c7cbbaba..fe6729c0d72 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1877,4 +1877,4 @@ module ts { return createTextChangeRange(createTextSpanFromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); } -} +} From 15f3b892973021045a5e8bfc0dbbfe60e0976446 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 00:41:12 -0800 Subject: [PATCH 21/50] Add the syntax cursor. We will use this to retrieve nodes from the previous source tree. --- src/compiler/parser.ts | 129 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 8 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f15de1a2ea5..ec78a807448 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -450,6 +450,104 @@ module ts { length: number } + // Allows finding nodes in the source file at a certain position in an efficient manner. + // The implementation takes advantage of the calling pattern it knows hte parser will + // make in order to optimize finding nodes as quickly as possible. + interface SyntaxCursor { + currentNode(position: number): IncrementalNode; + } + + function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { + var currentArray: NodeArray = sourceFile.statements; + var currentArrayIndex = 0; + + Debug.assert(currentArrayIndex < currentArray.length); + var current = currentArray[currentArrayIndex]; + var lastQueriedPosition = -1; + + return { + currentNode(position: number) { + // If the position is different than the last time we were asked. + if (position !== lastQueriedPosition) { + // Much of the time the parser will be need the very next node in the array + // that we just returned a node from. So just simply check for that case and + // move forward in the array instead of searching for the node again. + if (current && current.end === position && currentArrayIndex < currentArray.length) { + currentArrayIndex++; + current = currentArray[currentArrayIndex]; + } + + // If we don't have a node, or the node we have isn't in the right position, + // then try to find a viable node at the position requested. + if (!current || current.pos !== position) { + findHighestNodeAtPosition(position); + } + } + + // Cache this query so that we don't do any extra work if the parser calls back + // into us. Note: this is very common as the parser will make pairs of calls like + // 'isListElement -> parseListElement'. If we were unable to find a node when + // called with 'isListElement', we don't want to redo the work when parseListElement + // is called immediately after. + lastQueriedPosition = position; + return current; + } + }; + + function findHighestNodeAtPosition(position: number) { + // Clear out any cached state about the last node we found. + currentArray = undefined; + currentArrayIndex = -1; + current = undefined; + + // Recurse into the source file to find the highest node at this position. + forEachChild(sourceFile, visitNode, visitArray); + + function visitNode(node: Node) { + if (position >= node.pos && position < node.end) { + // Position was within this node. Keep searching deeper to find the node. + forEachChild(node, visitNode, visitArray); + + // don't procede any futher in the search. + return true; + } + + // position wasn't in this node, have to keep searching. + return false; + } + + function visitArray(array: NodeArray) { + if (position >= array.pos && position < array.end) { + // position was in this array. Search through this array to see if we find a + // viable element. + for (var i = 0, n = array.length; i < n; i++) { + var child = array[i]; + if (child) { + if (child.pos === position) { + // Found the right node. We're done. + currentArray = array; + currentArrayIndex = i; + current = child; + return true; + } + else { + if (child.pos > position && position < child.end) { + // Position in somewhere within this child. Search in it and + // stop searching in this array. + forEachChild(child, visitNode, visitArray); + return true; + } + } + } + } + } + + // position wasn't in this array, have to keep searching. + return false; + } + } + } + export function createSourceFile(filename: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false): SourceFile { var parsingContext: ParsingContext; var identifiers: Map; @@ -459,6 +557,7 @@ module ts { var syntacticDiagnostics: Diagnostic[]; var scanner: Scanner; var token: SyntaxKind; + var syntaxCursor: SyntaxCursor; // Flags that dictate what parsing context we're in. For example: // Whether or not we are in strict parsing mode. All that changes in strict parsing mode is @@ -604,6 +703,8 @@ module ts { return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); } + syntaxCursor = createSyntaxCursor(sourceFile); + // Make the actual change larger so that we know to reparse anything whose lookahead // might have intersected the change. var changeRange = extendToAffectedRange(textChangeRange); @@ -644,7 +745,12 @@ module ts { // inconsistent tree. Setting the parents on the new tree should be very fast. We // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. - return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); + var result = parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); + + // Clear out the syntax cursor so it doesn't keep anything alive longer than it should. + syntaxCursor = undefined; + + return result; } function updateTokenPositionsAndMarkElements(node: IncrementalNode, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number): void { @@ -1548,8 +1654,20 @@ module ts { return undefined; } - var node: Node = currentNodeFromCursor(); - if (!node) { + if (!syntaxCursor) { + // if we don't have a cursor, we could never return a node from the old tree. + return undefined; + } + + var node = syntaxCursor.currentNode(scanner.getTextPos()); + + // Can't reuse a missing node. + if (isMissingNode(node)) { + return undefined; + } + + // Can't reuse a node that intersected the change range. + if (node.intersectsChange) { return undefined; } @@ -1801,11 +1919,6 @@ module ts { return node.kind === SyntaxKind.Parameter; } - function currentNodeFromCursor(): Node { - // NYI - return undefined; - } - // Returns true if we should abort parsing. function abortParsingListOrMoveToNextToken(kind: ParsingContext) { parseErrorAtCurrentToken(parsingContextErrors(kind)); From 7eb0f42560d58254d596270f89b9686b5559fdbd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 00:48:08 -0800 Subject: [PATCH 22/50] Add assert. --- src/compiler/parser.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ec78a807448..ac014fea9e8 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -490,6 +490,9 @@ module ts { // called with 'isListElement', we don't want to redo the work when parseListElement // is called immediately after. lastQueriedPosition = position; + + // Either we don'd have a node, or we have a node at the position being asked for. + Debug.assert(!current || current.pos === position); return current; } }; From 62dd12cb7ac04467ed19fe06b70248773abecdeb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 00:56:14 -0800 Subject: [PATCH 23/50] Move functions from 'types.ts' to 'utilities.ts'. --- src/compiler/types.ts | 235 -------------------------------------- src/compiler/utilities.ts | 235 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+), 235 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2df24398d61..ee9694e0568 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1637,245 +1637,10 @@ module ts { intersection(span: TextSpan): TextSpan; } - var textSpanConstructor = (function () { - function textSpanConstructor(start: number, length: number) { - if (start < 0) { - throw new Error("start < 0"); - } - if (length < 0) { - throw new Error("start < 0"); - } - this._start = start; - this._length = length; - } - - textSpanConstructor.prototype = { - toJSON(key: string) { - return { start: this._start, length: this._length } - }, - start() { - return this._start - }, - length() { - return this._length - }, - end() { - return this._start + this._length - }, - isEmpty() { - return this._length === 0 - }, - containsPosition(position: number) { - return position >= this._start && position < this.end() - }, - containsTextSpan(span: TextSpan) { - return span.start() >= this._start && span.end() <= this.end() - }, - overlapsWith(span: TextSpan) { - var overlapStart = Math.max(this._start, span.start()); - var overlapEnd = Math.min(this.end(), span.end()); - return overlapStart < overlapEnd; - }, - overlap(span: TextSpan) { - var overlapStart = Math.max(this._start, span.start()); - var overlapEnd = Math.min(this.end(), span.end()); - if (overlapStart < overlapEnd) { - return createTextSpanFromBounds(overlapStart, overlapEnd); - } - return undefined; - }, - intersectsWithTextSpan(span: TextSpan) { - return span.start() <= this.end() && span.end() >= this._start - }, - intersectsWith(start: number, length: number) { - var end = start + length; - return start <= this.end() && end >= this._start; - }, - intersectsWithPosition(position: number) { - return position <= this.end() && position >= this._start; - }, - intersection(span: TextSpan) { - var intersectStart = Math.max(this._start, span.start()); - var intersectEnd = Math.min(this.end(), span.end()); - if (intersectStart <= intersectEnd) { - return createTextSpanFromBounds(intersectStart, intersectEnd); - } - return undefined; - } - }; - - return textSpanConstructor; - })(); - - export function createTextSpan(start: number, length: number): TextSpan { - return new (textSpanConstructor)(start, length); - } - - export function createTextSpanFromBounds(start: number, end: number) { - return createTextSpan(start, end - start); - } - export interface TextChangeRange { span(): TextSpan; newLength(): number; newSpan(): TextSpan; isUnchanged(): boolean; } - - var textChangeRangeConstructor = (function () { - function textChangeRangeConstructor(span: TextSpan, newLength: number) { - if (newLength < 0) { - throw new Error("newLength < 0"); - } - this._span = span; - this._newLength = newLength; - } - - textChangeRangeConstructor.prototype = { - span() { - return this._span; - }, - newLength() { - return this._newLength; - }, - newSpan() { - return createTextSpan(this.span().start(), this.newLength()); - }, - isUnchanged() { - return this.span().isEmpty() && this.newLength() === 0; - } - }; - - return textChangeRangeConstructor; - })(); - - export function createTextChangeRange(span: TextSpan, newLength: number): TextChangeRange { - return new (textChangeRangeConstructor)(span, newLength); - } - - export var 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. - var change0 = changes[0]; - - var oldStartN = change0.span().start(); - var oldEndN = change0.span().end(); - var newEndN = oldStartN + change0.newLength(); - - for (var i = 1; i < changes.length; i++) { - var 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)) - // } - - var oldStart1 = oldStartN; - var oldEnd1 = oldEndN; - var newEnd1 = newEndN; - - var oldStart2 = nextChange.span().start(); - var oldEnd2 = nextChange.span().end(); - var 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); - } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 94b51e52ba6..62d598ea2fc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -708,6 +708,7 @@ module ts { } } } + return undefined; } @@ -733,4 +734,238 @@ module ts { return false; } + var textSpanConstructor = (function () { + function textSpanConstructor(start: number, length: number) { + if (start < 0) { + throw new Error("start < 0"); + } + if (length < 0) { + throw new Error("start < 0"); + } + this._start = start; + this._length = length; + } + + textSpanConstructor.prototype = { + toJSON(key: string) { + return { start: this._start, length: this._length } + }, + start() { + return this._start + }, + length() { + return this._length + }, + end() { + return this._start + this._length + }, + isEmpty() { + return this._length === 0 + }, + containsPosition(position: number) { + return position >= this._start && position < this.end() + }, + containsTextSpan(span: TextSpan) { + return span.start() >= this._start && span.end() <= this.end() + }, + overlapsWith(span: TextSpan) { + var overlapStart = Math.max(this._start, span.start()); + var overlapEnd = Math.min(this.end(), span.end()); + return overlapStart < overlapEnd; + }, + overlap(span: TextSpan) { + var overlapStart = Math.max(this._start, span.start()); + var overlapEnd = Math.min(this.end(), span.end()); + if (overlapStart < overlapEnd) { + return createTextSpanFromBounds(overlapStart, overlapEnd); + } + return undefined; + }, + intersectsWithTextSpan(span: TextSpan) { + return span.start() <= this.end() && span.end() >= this._start + }, + intersectsWith(start: number, length: number) { + var end = start + length; + return start <= this.end() && end >= this._start; + }, + intersectsWithPosition(position: number) { + return position <= this.end() && position >= this._start; + }, + intersection(span: TextSpan) { + var intersectStart = Math.max(this._start, span.start()); + var intersectEnd = Math.min(this.end(), span.end()); + if (intersectStart <= intersectEnd) { + return createTextSpanFromBounds(intersectStart, intersectEnd); + } + return undefined; + } + }; + + return textSpanConstructor; + })(); + + export function createTextSpan(start: number, length: number): TextSpan { + return new (textSpanConstructor)(start, length); + } + + export function createTextSpanFromBounds(start: number, end: number) { + return createTextSpan(start, end - start); + } + + var textChangeRangeConstructor = (function () { + function textChangeRangeConstructor(span: TextSpan, newLength: number) { + if (newLength < 0) { + throw new Error("newLength < 0"); + } + this._span = span; + this._newLength = newLength; + } + + textChangeRangeConstructor.prototype = { + span() { + return this._span; + }, + newLength() { + return this._newLength; + }, + newSpan() { + return createTextSpan(this.span().start(), this.newLength()); + }, + isUnchanged() { + return this.span().isEmpty() && this.newLength() === 0; + } + }; + + return textChangeRangeConstructor; + })(); + + export function createTextChangeRange(span: TextSpan, newLength: number): TextChangeRange { + return new (textChangeRangeConstructor)(span, newLength); + } + + export var 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. + var change0 = changes[0]; + + var oldStartN = change0.span().start(); + var oldEndN = change0.span().end(); + var newEndN = oldStartN + change0.newLength(); + + for (var i = 1; i < changes.length; i++) { + var 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)) + // } + + var oldStart1 = oldStartN; + var oldEnd1 = oldEndN; + var newEnd1 = newEndN; + + var oldStart2 = nextChange.span().start(); + var oldEnd2 = nextChange.span().end(); + var 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); + } } \ No newline at end of file From 2f833d5f97d348f225b903d2969554e24021f948 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 02:21:45 -0800 Subject: [PATCH 24/50] Provide a way for tests to try out incremental parsing. --- src/compiler/parser.ts | 13 +++++++------ src/harness/harnessLanguageService.ts | 2 +- src/services/services.ts | 14 ++++++++------ tests/cases/unittests/incrementalParser.ts | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ac014fea9e8..a3d9fb51fca 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -641,9 +641,9 @@ module ts { var sourceFile: SourceFile; - return parseSourceFile(sourceText, /*textChangeRange:*/ undefined, setParentNodes); + return parseSourceFile(sourceText, setParentNodes); - function parseSourceFile(text: string, textChangeRange: TextChangeRange, setParentNodes: boolean): SourceFile { + function parseSourceFile(text: string, setParentNodes: boolean): SourceFile { // Set our initial state before parsing. sourceText = text; parsingContext = 0; @@ -703,7 +703,7 @@ module ts { if (sourceFile.statements.length === 0) { // If we don't have any statements in the current source file, hten there's no real // way to incrementally parse. So just do a full parse instead. - return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); + return parseSourceFile(newText, /*setNodeParents*/ true); } syntaxCursor = createSyntaxCursor(sourceFile); @@ -739,8 +739,9 @@ module ts { updateTokenPositionsAndMarkElements(sourceFile, changeRange.span().start(), changeRange.span().end(), changeRange.newSpan().end(), delta); - // Don't pass along the text change range for now. We'll pass it along once incremental - // parsing is enabled. + // Now that we've set up our internal incremental state just proceed and parse the + // source file in the normal fashion. When possible the parser will retrieve and + // reuse nodes from the old tree. // // Note: passing in 'true' for setNodeParents is very important. When incrementally // parsing, we will be reusing nodes from the old tree, and placing it into new @@ -748,7 +749,7 @@ module ts { // inconsistent tree. Setting the parents on the new tree should be very fast. We // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. - var result = parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); + var result = parseSourceFile(newText, /*setNodeParents*/ true); // Clear out the syntax cursor so it doesn't keep anything alive longer than it should. syntaxCursor = undefined; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 430dcc24477..e850fb57c01 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -126,7 +126,7 @@ module Harness.LanguageService { isOpen: boolean, textChangeRange: ts.TextChangeRange ): ts.SourceFile { - return ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, isOpen, textChangeRange); + return ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, isOpen, textChangeRange, /*useIncremental:*/ false); } public releaseDocument(fileName: string, compilationSettings: ts.CompilerOptions): void { diff --git a/src/services/services.ts b/src/services/services.ts index 6d1a7c357ef..0c3abf090ae 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1519,7 +1519,7 @@ module ts { var editRange = this.hostCache.getChangeRange(filename, this.currentFileVersion, this.currentSourceFile.scriptSnapshot); var start = new Date().getTime(); - sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, /*isOpen*/ true, editRange); + sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, /*isOpen*/ true, editRange, /*useIncremental:*/ false); this.host.log("SyntaxTreeCache.Initialize: updateSourceFile: " + (new Date().getTime() - start)); } @@ -1553,7 +1553,7 @@ module ts { return sourceFile; } - export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile { + export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange, useIncremental: boolean): SourceFile { if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { var oldText = sourceFile.scriptSnapshot; var newText = scriptSnapshot; @@ -1576,9 +1576,11 @@ module ts { if (textChangeRange) { if (version !== sourceFile.version || isOpen != sourceFile.isOpen) { // Once incremental parsing is ready, then just call into this function. - // var newSourceFile = sourceFile.update(scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange); - // setSourceFileFields(newSourceFile, scriptSnapshot, version, isOpen); - // return newSourceFile; + if (useIncremental) { + var newSourceFile = sourceFile.update(scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange); + setSourceFileFields(newSourceFile, scriptSnapshot, version, isOpen); + return newSourceFile; + } } } @@ -1661,7 +1663,7 @@ module ts { var entry = lookUp(bucket, filename); Debug.assert(entry !== undefined); - entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, isOpen, textChangeRange); + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, isOpen, textChangeRange, /*useIncremental:*/ false); return entry.sourceFile; } diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index a1ab175e114..ddbd8acfc02 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -55,7 +55,7 @@ module ts { Utils.assertInvariants(newTree, /*parent:*/ undefined); // Create a tree for the new text, in an incremental fashion. - var incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange); + var incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange, /*useIncremental:*/ false); Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); // We should get the same tree when doign a full or incremental parse. From 9c0e4211bc29de5ba6b1154366277e1a456fa022 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 02:34:53 -0800 Subject: [PATCH 25/50] Properly adjust nodes while walking down the tree. --- src/compiler/parser.ts | 4 ++-- tests/cases/unittests/incrementalParser.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a3d9fb51fca..d60a5415577 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -801,7 +801,7 @@ module ts { // Adjust the pos or end (or both) of the intersecting array accordingly. adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); for (var i = 0, n = array.length; i < n; i++) { - forEachChild(array[i], visitNode, visitArray); + visitNode(array[i]); } } // else { @@ -908,7 +908,7 @@ module ts { array.end += delta; for (var i = 0, n = array.length; i < n; i++) { - forEachChild(array[i], visitNode, visitArray); + visitNode(array[i]); } } } diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index ddbd8acfc02..fd4d31197d7 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -55,7 +55,7 @@ module ts { Utils.assertInvariants(newTree, /*parent:*/ undefined); // Create a tree for the new text, in an incremental fashion. - var incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange, /*useIncremental:*/ false); + var incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange, /*useIncremental:*/ true); Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); // We should get the same tree when doign a full or incremental parse. @@ -177,7 +177,9 @@ module ts { } describe('Incremental',() => { + debugger; it('Inserting into method',() => { + debugger; var source = "class C {\r\n" + " public foo1() { }\r\n" + " public foo2() {\r\n" + From e32d030144d03802d438a98310d73f556239f468 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 02:38:07 -0800 Subject: [PATCH 26/50] Update the source file positions as well. --- src/compiler/parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d60a5415577..1352f5c71c5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -758,7 +758,7 @@ module ts { } function updateTokenPositionsAndMarkElements(node: IncrementalNode, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number): void { - forEachChild(node, visitNode, visitArray); + visitNode(node); function visitNode(child: IncrementalNode) { if (child.pos > changeRangeOldEnd) { From 60c62e5b6bde26897d4d456c95dbdf885194cf3f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 03:06:05 -0800 Subject: [PATCH 27/50] Don't consume nodes during calls to isListElement. --- src/compiler/parser.ts | 26 ++++++++++++------- tests/cases/unittests/incrementalParser.ts | 30 +++++++++++----------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1352f5c71c5..ffe23c9d36d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -534,7 +534,7 @@ module ts { return true; } else { - if (child.pos > position && position < child.end) { + if (child.pos < position && position < child.end) { // Position in somewhere within this child. Search in it and // stop searching in this array. forEachChild(child, visitNode, visitArray); @@ -1616,7 +1616,7 @@ module ts { while (!isListTerminator(kind)) { if (isListElement(kind, /* inErrorRecovery */ false)) { - var element = currentNode(kind) || parseElement(); + var element = parseListElement(kind, parseElement); result.push(element); // test elements only if we are not already in strict mode @@ -1646,6 +1646,15 @@ module ts { return result; } + function parseListElement(kind: ParsingContext, parseElement: () => T): T { + var node = currentNode(kind); + if (node) { + return consumeNode(node); + } + + return parseElement(); + } + function currentNode(parsingContext: ParsingContext): Node { // If there is an outstanding parse error that we've encountered, but not attached to // some node, then we cannot get a node from the old source tree. This is because we @@ -1663,7 +1672,7 @@ module ts { return undefined; } - var node = syntaxCursor.currentNode(scanner.getTextPos()); + var node = syntaxCursor.currentNode(scanner.getStartPos()); // Can't reuse a missing node. if (isMissingNode(node)) { @@ -1686,7 +1695,8 @@ module ts { // differently depending on what mode it is in. // // This also applies to all our other context flags as well. - if (node.parserContextFlags !== contextFlags) { + var nodeContextFlags = node.parserContextFlags || 0; + if (nodeContextFlags !== contextFlags) { return undefined; } @@ -1696,13 +1706,11 @@ module ts { return undefined; } - // It was valid. Let teh source know we're consuming this node, and pass to the list - // parser. - return consumeNode(node); + return node; } function consumeNode(node: Node) { - // Move the scanner so it is after the node we just consumed + // Move the scanner so it is after the node we just consumed. scanner.setTextPos(node.end); nextToken(); return node; @@ -1944,7 +1952,7 @@ module ts { var commaStart = -1; // Meaning the previous token was not a comma while (true) { if (isListElement(kind, /* inErrorRecovery */ false)) { - result.push(currentNode(kind) || parseElement()); + result.push(parseListElement(kind, parseElement)); commaStart = scanner.getTokenPos(); if (parseOptional(SyntaxKind.CommaToken)) { continue; diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index fd4d31197d7..8a57556363d 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -192,7 +192,7 @@ module ts { var semicolonIndex = source.indexOf(";"); var newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); }); it('Deleting from method',() => { @@ -208,7 +208,7 @@ module ts { var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withDelete(oldText, index, "+ 1".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); }); it('Regular expression 1',() => { @@ -228,7 +228,7 @@ module ts { var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withInsert(oldText, semicolonIndex, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); }); it('Comment 1',() => { @@ -266,7 +266,7 @@ module ts { var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withInsert(oldText, index, "*"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); }); it('Parameter 1',() => { @@ -281,7 +281,7 @@ module ts { var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); }); it('Type member 1',() => { @@ -292,7 +292,7 @@ module ts { var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withInsert(oldText, index, "?"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); }); it('Enum element 1',() => { @@ -303,7 +303,7 @@ module ts { var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withChange(oldText, index, 2, "+"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 21); }); it('Strict mode 1',() => { @@ -600,7 +600,7 @@ module ts { var index = source.lastIndexOf(";"); var newTextAndChange = withDelete(oldText, index, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); }); it('Edit after empty type parameter list',() => { @@ -634,7 +634,7 @@ var o2 = { set Foo(val:number) { } };"; var index = source.indexOf("set"); var newTextAndChange = withInsert(oldText, index, "public "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 6); }); it('Insert parameter ahead of parameter',() => { @@ -650,7 +650,7 @@ constructor(name) { }\ var index = source.indexOf("100"); var newTextAndChange = withInsert(oldText, index, "'1', "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 5); }); it('Insert declare modifier before module',() => { @@ -663,7 +663,7 @@ module m3 { }\ var index = 0; var newTextAndChange = withInsert(oldText, index, "declare "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 3); }); it('Insert function above arrow function with comment',() => { @@ -719,7 +719,7 @@ module m3 { }\ var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withInsert(oldText, 0, ""); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); }); it('Class to interface',() => { @@ -782,7 +782,7 @@ module m3 { }\ var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withChange(oldText, 0, "class".length, "interface"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); }); it('Moving index signatures from interface to class',() => { @@ -791,7 +791,7 @@ module m3 { }\ var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withChange(oldText, 0, "interface".length, "class"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); }); it('Moving accessors from class to object literal',() => { @@ -809,7 +809,7 @@ module m3 { }\ var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 16); }); // Simulated typing tests. From 8820ca059683d4aba69785288fc9080c64c68f8a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 03:25:49 -0800 Subject: [PATCH 28/50] Change the error-bit to be a node-flag and not a parser context flag. Do not reuse nodes with errors in them. We need to reparse them to make sure we produce the right errors the second time around. --- src/compiler/parser.ts | 10 ++++- src/compiler/types.ts | 46 +++++++++++----------- src/compiler/utilities.ts | 10 ++--- tests/cases/unittests/incrementalParser.ts | 11 ++++-- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ffe23c9d36d..37565a89843 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1335,7 +1335,7 @@ module ts { // flag so that we don't mark any subsequent nodes. if (parseErrorBeforeNextFinishedNode) { parseErrorBeforeNextFinishedNode = false; - node.parserContextFlags |= ParserContextFlags.ContainsError; + node.flags |= NodeFlags.ContainsError; } return node; @@ -1684,6 +1684,12 @@ module ts { return undefined; } + // Can't reuse a node that contains a parse error. This is necessary so that we + // produce the same set of errors again. + if (containsParseError(node)) { + return undefined; + } + // We can only reuse a node if it was parsed under the same strict mode that we're // currently in. i.e. if we originally parsed a node in non-strict mode, but then // the user added 'using strict' at the top of the file, then we can't use that node @@ -1695,7 +1701,7 @@ module ts { // differently depending on what mode it is in. // // This also applies to all our other context flags as well. - var nodeContextFlags = node.parserContextFlags || 0; + var nodeContextFlags = node.parserContextFlags & ParserContextFlags.FlagsMask; if (nodeContextFlags !== contextFlags) { return undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ee9694e0568..e7d215890d9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -281,18 +281,29 @@ module ts { } export const enum NodeFlags { - Export = 0x00000001, // Declarations - Ambient = 0x00000002, // Declarations - Public = 0x00000010, // Property/Method - Private = 0x00000020, // Property/Method - Protected = 0x00000040, // Property/Method - Static = 0x00000080, // Property/Method - MultiLine = 0x00000100, // Multi-line array or object literal - Synthetic = 0x00000200, // Synthetic node (for full fidelity) - DeclarationFile = 0x00000400, // Node is a .d.ts file - Let = 0x00000800, // Variable declaration - Const = 0x00001000, // Variable declaration - OctalLiteral = 0x00002000, + Export = 0x00000001, // Declarations + Ambient = 0x00000002, // Declarations + Public = 0x00000010, // Property/Method + Private = 0x00000020, // Property/Method + Protected = 0x00000040, // Property/Method + Static = 0x00000080, // Property/Method + MultiLine = 0x00000100, // Multi-line array or object literal + Synthetic = 0x00000200, // Synthetic node (for full fidelity) + DeclarationFile = 0x00000400, // Node is a .d.ts file + Let = 0x00000800, // Variable declaration + Const = 0x00001000, // Variable declaration + OctalLiteral = 0x00002000, + + // If the parser encountered an error when parsing the code that created this node. Note + // the parser only sets this directly on the node it creates right after encountering the + // error. We then propagate that flag upwards to parent nodes during incremental parsing. + ContainsError = 0x00004000, + + // Used during incremental parsing to determine if we need to visit this node to see if + // any of its children had an error. Once we compute that once, we can set this bit on the + // node to know that we never have to do it again. From that point on, we can just check + // the node directly for 'ContainsError'. + HasPropagatedChildContainsErrorFlag = 0x00008000, Modifier = Export | Ambient | Public | Private | Protected | Static, AccessibilityModifier = Public | Private | Protected, @@ -313,16 +324,7 @@ module ts { // If this node was parsed in the parameters of a generator. GeneratorParameter = 1 << 3, - // If the parser encountered an error when parsing the code that created this node. Note - // the parser only sets this directly on the node it creates right after encountering the - // error. We then propagate that flag upwards to parent nodes during incremental parsing. - ContainsError = 1 << 4, - - // Used during incremental parsing to determine if we need to visit this node to see if - // any of its children had an error. Once we compute that once, we can set this bit on the - // node to know that we never have to do it again. From that point on, we can just check - // the node directly for 'ContainsError'. - HasPropagatedChildContainsErrorFlag = 1 << 5 + FlagsMask = StrictMode | DisallowIn | Yield | GeneratorParameter, } export interface Node extends TextRange { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 62d598ea2fc..d99127c9483 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -68,25 +68,25 @@ module ts { // Returns true if this node contains a parse error anywhere underneath it. export function containsParseError(node: Node): boolean { - if (!hasFlag(node.parserContextFlags, ParserContextFlags.HasPropagatedChildContainsErrorFlag)) { + if (!hasFlag(node.flags, NodeFlags.HasPropagatedChildContainsErrorFlag)) { // 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. - var val = hasFlag(node.parserContextFlags, ParserContextFlags.ContainsError) || + var val = hasFlag(node.flags, NodeFlags.ContainsError) || forEachChild(node, containsParseError); // If so, mark ourselves accordingly. if (val) { - node.parserContextFlags |= ParserContextFlags.ContainsError; + node.flags |= NodeFlags.ContainsError; } // 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.HasPropagatedChildContainsErrorFlag; + node.flags |= NodeFlags.HasPropagatedChildContainsErrorFlag; } - return hasFlag(node.parserContextFlags, ParserContextFlags.ContainsError); + return hasFlag(node.flags, NodeFlags.ContainsError); } export function getSourceFileOfNode(node: Node): SourceFile { diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index 8a57556363d..282f126344b 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -88,6 +88,11 @@ module ts { assert.equal(node1.pos, node2.pos, "node1.pos !== node2.pos"); assert.equal(node1.end, node2.end, "node1.end !== node2.end"); assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind"); + + // call this on both nodes to ensure all propagated flags have been set (and thus can be + // compared). + ts.containsParseError(node1); + ts.containsParseError(node2); assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags"); assert.equal(node1.parserContextFlags, node2.parserContextFlags, "node1.parserContextFlags !== node2.parserContextFlags"); @@ -177,7 +182,6 @@ module ts { } describe('Incremental',() => { - debugger; it('Inserting into method',() => { debugger; var source = "class C {\r\n" + @@ -768,12 +772,13 @@ module m3 { }\ }); it('Moving methods from object literal to class',() => { + debugger; var source = "var v = { public A() { } public B() { } public C() { } }" var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); }); it('Moving index signatures from class to interface',() => { @@ -809,7 +814,7 @@ module m3 { }\ var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 16); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); }); // Simulated typing tests. From d5c663685402a623e36a60f069f3025dcec77378 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Dec 2014 03:46:32 -0800 Subject: [PATCH 29/50] Parse function and variable declarations uniformly, whether they're at teh top level, or inside a method. This is necessary for incremental parsing correctness, as the incremental parser will attempt to reuse these types of nodes in both contexts, and we much ensure it creates the same trees you would get if you were parsing normally. --- src/compiler/parser.ts | 12 +++++++++ .../reference/anonymousModules.errors.txt | 25 ++---------------- ...functionsWithModifiersInBlocks1.errors.txt | 26 +++++++++++++++++++ .../reference/innerModExport1.errors.txt | 11 +------- .../reference/innerModExport2.errors.txt | 17 +++++------- ...rserModifierOnStatementInBlock1.errors.txt | 11 ++++---- ...rserModifierOnStatementInBlock3.errors.txt | 12 ++++----- ...rserModifierOnStatementInBlock4.errors.txt | 13 +++------- .../functionsWithModifiersInBlocks1.ts | 5 ++++ tests/cases/unittests/incrementalParser.ts | 5 ++-- 10 files changed, 69 insertions(+), 68 deletions(-) create mode 100644 tests/baselines/reference/functionsWithModifiersInBlocks1.errors.txt create mode 100644 tests/cases/compiler/functionsWithModifiersInBlocks1.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 37565a89843..291eeb8c4ba 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3965,6 +3965,18 @@ module ts { } function isStartOfStatement(inErrorRecovery: boolean): boolean { + // Functions and variable statements are allowed as a statement. But as per the grammar, + // they also allow modifiers. So we have to check for those statements that might be + // following modifiers.This ensures that things work properly when incrementally parsing + // as the parser will produce the same FunctionDeclaraiton or VariableStatement if it has + // the same text regardless of whether it is inside a block or not. + if (isModifier(token)) { + var result = lookAhead(parseVariableStatementOrFunctionDeclarationWithModifiers); + if (result) { + return true; + } + } + switch (token) { case SyntaxKind.SemicolonToken: // If we're in error recovery, then we don't want to treat ';' as an empty statement. diff --git a/tests/baselines/reference/anonymousModules.errors.txt b/tests/baselines/reference/anonymousModules.errors.txt index 43b3d544a8b..1d63cc41c50 100644 --- a/tests/baselines/reference/anonymousModules.errors.txt +++ b/tests/baselines/reference/anonymousModules.errors.txt @@ -1,29 +1,18 @@ tests/cases/compiler/anonymousModules.ts(1,8): error TS1005: ';' expected. -tests/cases/compiler/anonymousModules.ts(2,2): error TS1129: Statement expected. -tests/cases/compiler/anonymousModules.ts(2,2): error TS1148: Cannot compile external modules unless the '--module' flag is provided. tests/cases/compiler/anonymousModules.ts(4,9): error TS1005: ';' expected. -tests/cases/compiler/anonymousModules.ts(5,3): error TS1129: Statement expected. -tests/cases/compiler/anonymousModules.ts(6,2): error TS1128: Declaration or statement expected. tests/cases/compiler/anonymousModules.ts(10,9): error TS1005: ';' expected. -tests/cases/compiler/anonymousModules.ts(13,1): error TS1128: Declaration or statement expected. tests/cases/compiler/anonymousModules.ts(1,1): error TS2304: Cannot find name 'module'. tests/cases/compiler/anonymousModules.ts(4,2): error TS2304: Cannot find name 'module'. -tests/cases/compiler/anonymousModules.ts(5,14): error TS2395: Individual declarations in merged declaration bar must be all exported or all local. -tests/cases/compiler/anonymousModules.ts(8,6): error TS2395: Individual declarations in merged declaration bar must be all exported or all local. tests/cases/compiler/anonymousModules.ts(10,2): error TS2304: Cannot find name 'module'. -==== tests/cases/compiler/anonymousModules.ts (13 errors) ==== +==== tests/cases/compiler/anonymousModules.ts (6 errors) ==== module { ~ !!! error TS1005: ';' expected. ~~~~~~ !!! error TS2304: Cannot find name 'module'. export var foo = 1; - ~~~~~~ -!!! error TS1129: Statement expected. - ~~~~~~~~~~~~~~~~~~~ -!!! error TS1148: Cannot compile external modules unless the '--module' flag is provided. module { ~ @@ -31,17 +20,9 @@ tests/cases/compiler/anonymousModules.ts(10,2): error TS2304: Cannot find name ' ~~~~~~ !!! error TS2304: Cannot find name 'module'. export var bar = 1; - ~~~~~~ -!!! error TS1129: Statement expected. - ~~~ -!!! error TS2395: Individual declarations in merged declaration bar must be all exported or all local. } - ~ -!!! error TS1128: Declaration or statement expected. var bar = 2; - ~~~ -!!! error TS2395: Individual declarations in merged declaration bar must be all exported or all local. module { ~ @@ -50,6 +31,4 @@ tests/cases/compiler/anonymousModules.ts(10,2): error TS2304: Cannot find name ' !!! error TS2304: Cannot find name 'module'. var x = bar; } - } - ~ -!!! error TS1128: Declaration or statement expected. \ No newline at end of file + } \ No newline at end of file diff --git a/tests/baselines/reference/functionsWithModifiersInBlocks1.errors.txt b/tests/baselines/reference/functionsWithModifiersInBlocks1.errors.txt new file mode 100644 index 00000000000..557c67e496a --- /dev/null +++ b/tests/baselines/reference/functionsWithModifiersInBlocks1.errors.txt @@ -0,0 +1,26 @@ +tests/cases/compiler/functionsWithModifiersInBlocks1.ts(2,4): error TS1184: Modifiers cannot appear here. +tests/cases/compiler/functionsWithModifiersInBlocks1.ts(3,4): error TS1184: Modifiers cannot appear here. +tests/cases/compiler/functionsWithModifiersInBlocks1.ts(4,12): error TS1029: 'export' modifier must precede 'declare' modifier. +tests/cases/compiler/functionsWithModifiersInBlocks1.ts(2,21): error TS2393: Duplicate function implementation. +tests/cases/compiler/functionsWithModifiersInBlocks1.ts(3,20): error TS2393: Duplicate function implementation. +tests/cases/compiler/functionsWithModifiersInBlocks1.ts(4,28): error TS2393: Duplicate function implementation. + + +==== tests/cases/compiler/functionsWithModifiersInBlocks1.ts (6 errors) ==== + { + declare function f() { } + ~~~~~~~ +!!! error TS1184: Modifiers cannot appear here. + ~ +!!! error TS2393: Duplicate function implementation. + export function f() { } + ~~~~~~ +!!! error TS1184: Modifiers cannot appear here. + ~ +!!! error TS2393: Duplicate function implementation. + declare export function f() { } + ~~~~~~ +!!! error TS1029: 'export' modifier must precede 'declare' modifier. + ~ +!!! error TS2393: Duplicate function implementation. + } \ No newline at end of file diff --git a/tests/baselines/reference/innerModExport1.errors.txt b/tests/baselines/reference/innerModExport1.errors.txt index 11e9cdc01d1..9e5840f2ccd 100644 --- a/tests/baselines/reference/innerModExport1.errors.txt +++ b/tests/baselines/reference/innerModExport1.errors.txt @@ -1,11 +1,8 @@ tests/cases/compiler/innerModExport1.ts(5,12): error TS1005: ';' expected. -tests/cases/compiler/innerModExport1.ts(7,9): error TS1129: Statement expected. -tests/cases/compiler/innerModExport1.ts(14,5): error TS1148: Cannot compile external modules unless the '--module' flag is provided. -tests/cases/compiler/innerModExport1.ts(17,1): error TS1128: Declaration or statement expected. tests/cases/compiler/innerModExport1.ts(5,5): error TS2304: Cannot find name 'module'. -==== tests/cases/compiler/innerModExport1.ts (5 errors) ==== +==== tests/cases/compiler/innerModExport1.ts (2 errors) ==== module Outer { // inner mod 1 @@ -17,8 +14,6 @@ tests/cases/compiler/innerModExport1.ts(5,5): error TS2304: Cannot find name 'mo !!! error TS2304: Cannot find name 'module'. var non_export_var = 0; export var export_var = 1; - ~~~~~~ -!!! error TS1129: Statement expected. function NonExportFunc() { return 0; } @@ -26,12 +21,8 @@ tests/cases/compiler/innerModExport1.ts(5,5): error TS2304: Cannot find name 'mo } export var outer_var_export = 0; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS1148: Cannot compile external modules unless the '--module' flag is provided. export function outerFuncExport() { return 0; } } - ~ -!!! error TS1128: Declaration or statement expected. Outer.ExportFunc(); \ No newline at end of file diff --git a/tests/baselines/reference/innerModExport2.errors.txt b/tests/baselines/reference/innerModExport2.errors.txt index 1adcc46d441..79ba22e58ab 100644 --- a/tests/baselines/reference/innerModExport2.errors.txt +++ b/tests/baselines/reference/innerModExport2.errors.txt @@ -1,12 +1,11 @@ tests/cases/compiler/innerModExport2.ts(5,12): error TS1005: ';' expected. -tests/cases/compiler/innerModExport2.ts(7,9): error TS1129: Statement expected. -tests/cases/compiler/innerModExport2.ts(15,5): error TS1148: Cannot compile external modules unless the '--module' flag is provided. -tests/cases/compiler/innerModExport2.ts(18,1): error TS1128: Declaration or statement expected. tests/cases/compiler/innerModExport2.ts(5,5): error TS2304: Cannot find name 'module'. +tests/cases/compiler/innerModExport2.ts(7,20): error TS2395: Individual declarations in merged declaration export_var must be all exported or all local. +tests/cases/compiler/innerModExport2.ts(13,9): error TS2395: Individual declarations in merged declaration export_var must be all exported or all local. tests/cases/compiler/innerModExport2.ts(20,7): error TS2339: Property 'NonExportFunc' does not exist on type 'typeof Outer'. -==== tests/cases/compiler/innerModExport2.ts (6 errors) ==== +==== tests/cases/compiler/innerModExport2.ts (5 errors) ==== module Outer { // inner mod 1 @@ -18,23 +17,21 @@ tests/cases/compiler/innerModExport2.ts(20,7): error TS2339: Property 'NonExport !!! error TS2304: Cannot find name 'module'. var non_export_var = 0; export var export_var = 1; - ~~~~~~ -!!! error TS1129: Statement expected. + ~~~~~~~~~~ +!!! error TS2395: Individual declarations in merged declaration export_var must be all exported or all local. function NonExportFunc() { return 0; } export function ExportFunc() { return 0; } } var export_var: number; + ~~~~~~~~~~ +!!! error TS2395: Individual declarations in merged declaration export_var must be all exported or all local. export var outer_var_export = 0; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS1148: Cannot compile external modules unless the '--module' flag is provided. export function outerFuncExport() { return 0; } } - ~ -!!! error TS1128: Declaration or statement expected. Outer.NonExportFunc(); ~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/parserModifierOnStatementInBlock1.errors.txt b/tests/baselines/reference/parserModifierOnStatementInBlock1.errors.txt index c24bc0997c6..5fa104a0e83 100644 --- a/tests/baselines/reference/parserModifierOnStatementInBlock1.errors.txt +++ b/tests/baselines/reference/parserModifierOnStatementInBlock1.errors.txt @@ -1,16 +1,15 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock1.ts(1,1): error TS1148: Cannot compile external modules unless the '--module' flag is provided. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock1.ts(2,4): error TS1129: Statement expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock1.ts(3,1): error TS1128: Declaration or statement expected. +tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock1.ts(2,4): error TS1184: Modifiers cannot appear here. -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock1.ts (3 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock1.ts (2 errors) ==== export function foo() { ~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS1148: Cannot compile external modules unless the '--module' flag is provided. export var x = this; + ~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ -!!! error TS1129: Statement expected. +!!! error TS1184: Modifiers cannot appear here. } ~ -!!! error TS1128: Declaration or statement expected. +!!! error TS1148: Cannot compile external modules unless the '--module' flag is provided. \ No newline at end of file diff --git a/tests/baselines/reference/parserModifierOnStatementInBlock3.errors.txt b/tests/baselines/reference/parserModifierOnStatementInBlock3.errors.txt index ebb793783a2..5bc5a49670e 100644 --- a/tests/baselines/reference/parserModifierOnStatementInBlock3.errors.txt +++ b/tests/baselines/reference/parserModifierOnStatementInBlock3.errors.txt @@ -1,17 +1,17 @@ tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock3.ts(1,1): error TS1148: Cannot compile external modules unless the '--module' flag is provided. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock3.ts(2,4): error TS1129: Statement expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock3.ts(4,1): error TS1128: Declaration or statement expected. +tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock3.ts(2,4): error TS1184: Modifiers cannot appear here. -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock3.ts (3 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock3.ts (2 errors) ==== export function foo() { ~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS1148: Cannot compile external modules unless the '--module' flag is provided. export function bar() { + ~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ -!!! error TS1129: Statement expected. +!!! error TS1184: Modifiers cannot appear here. } + ~~~~ } ~ -!!! error TS1128: Declaration or statement expected. +!!! error TS1148: Cannot compile external modules unless the '--module' flag is provided. \ No newline at end of file diff --git a/tests/baselines/reference/parserModifierOnStatementInBlock4.errors.txt b/tests/baselines/reference/parserModifierOnStatementInBlock4.errors.txt index b84f88c0b9a..4fc38af7d5d 100644 --- a/tests/baselines/reference/parserModifierOnStatementInBlock4.errors.txt +++ b/tests/baselines/reference/parserModifierOnStatementInBlock4.errors.txt @@ -1,18 +1,11 @@ -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock4.ts(2,4): error TS1129: Statement expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock4.ts(2,4): error TS1148: Cannot compile external modules unless the '--module' flag is provided. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock4.ts(4,1): error TS1128: Declaration or statement expected. +tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock4.ts(2,4): error TS1184: Modifiers cannot appear here. -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock4.ts (3 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserModifierOnStatementInBlock4.ts (1 errors) ==== { export function bar() { ~~~~~~ -!!! error TS1129: Statement expected. - ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS1184: Modifiers cannot appear here. } - ~~~~ -!!! error TS1148: Cannot compile external modules unless the '--module' flag is provided. } - ~ -!!! error TS1128: Declaration or statement expected. \ No newline at end of file diff --git a/tests/cases/compiler/functionsWithModifiersInBlocks1.ts b/tests/cases/compiler/functionsWithModifiersInBlocks1.ts new file mode 100644 index 00000000000..2a03d962ffb --- /dev/null +++ b/tests/cases/compiler/functionsWithModifiersInBlocks1.ts @@ -0,0 +1,5 @@ +{ + declare function f() { } + export function f() { } + declare export function f() { } +} \ No newline at end of file diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index 282f126344b..1628325371f 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -183,7 +183,6 @@ module ts { describe('Incremental',() => { it('Inserting into method',() => { - debugger; var source = "class C {\r\n" + " public foo1() { }\r\n" + " public foo2() {\r\n" + @@ -745,12 +744,13 @@ module m3 { }\ }); it('Surrounding function declarations with block',() => { + debugger; var source = "declare function F1() { } export function F2() { } declare export function F3() { }" var oldText = ScriptSnapshot.fromString(source); var newTextAndChange = withInsert(oldText, 0, "{"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); }); it('Removing block around function declarations',() => { @@ -772,7 +772,6 @@ module m3 { }\ }); it('Moving methods from object literal to class',() => { - debugger; var source = "var v = { public A() { } public B() { } public C() { } }" var oldText = ScriptSnapshot.fromString(source); From 3478099a853d53ea166e0467c6b19293bf5325b9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Dec 2014 12:30:02 -0800 Subject: [PATCH 30/50] Add incremental parsing LS test. --- .../incrementalParsingInsertIntoMethod1.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts diff --git a/tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts b/tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts new file mode 100644 index 00000000000..930c0511ffb --- /dev/null +++ b/tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts @@ -0,0 +1,13 @@ +/// + +////class C { +//// public foo1() { } +//// public foo2() { +//// return 1/*1*/; +//// } +//// public foo3() { } +////} + +debugger; +goTo.marker("1"); +edit.insert(" + 1"); \ No newline at end of file From bd76ebd02bb3fef39c7e7931a247cdb1dce3ae8b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Dec 2014 12:39:11 -0800 Subject: [PATCH 31/50] Enable incremental parsing by default. Provide flag to disable incremental parsing if necessary. --- src/harness/harnessLanguageService.ts | 2 +- src/services/services.ts | 10 ++++++---- tests/cases/unittests/incrementalParser.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index e850fb57c01..430dcc24477 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -126,7 +126,7 @@ module Harness.LanguageService { isOpen: boolean, textChangeRange: ts.TextChangeRange ): ts.SourceFile { - return ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, isOpen, textChangeRange, /*useIncremental:*/ false); + return ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, isOpen, textChangeRange); } public releaseDocument(fileName: string, compilationSettings: ts.CompilerOptions): void { diff --git a/src/services/services.ts b/src/services/services.ts index 0c3abf090ae..163b8d75ded 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1519,7 +1519,7 @@ module ts { var editRange = this.hostCache.getChangeRange(filename, this.currentFileVersion, this.currentSourceFile.scriptSnapshot); var start = new Date().getTime(); - sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, /*isOpen*/ true, editRange, /*useIncremental:*/ false); + sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, /*isOpen*/ true, editRange); this.host.log("SyntaxTreeCache.Initialize: updateSourceFile: " + (new Date().getTime() - start)); } @@ -1553,7 +1553,9 @@ module ts { return sourceFile; } - export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange, useIncremental: boolean): SourceFile { + export var disableIncrementalParsing = false; + + export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile { if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { var oldText = sourceFile.scriptSnapshot; var newText = scriptSnapshot; @@ -1576,7 +1578,7 @@ module ts { if (textChangeRange) { if (version !== sourceFile.version || isOpen != sourceFile.isOpen) { // Once incremental parsing is ready, then just call into this function. - if (useIncremental) { + if (!disableIncrementalParsing) { var newSourceFile = sourceFile.update(scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange); setSourceFileFields(newSourceFile, scriptSnapshot, version, isOpen); return newSourceFile; @@ -1663,7 +1665,7 @@ module ts { var entry = lookUp(bucket, filename); Debug.assert(entry !== undefined); - entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, isOpen, textChangeRange, /*useIncremental:*/ false); + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, isOpen, textChangeRange); return entry.sourceFile; } diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index a236776a45d..263c396842d 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -55,7 +55,7 @@ module ts { Utils.assertInvariants(newTree, /*parent:*/ undefined); // Create a tree for the new text, in an incremental fashion. - var incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange, /*useIncremental:*/ true); + var incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange); Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); // We should get the same tree when doign a full or incremental parse. From fa4fab8a156e50ed79c8e0aa52da6172011e8733 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Dec 2014 19:37:15 -0800 Subject: [PATCH 32/50] Change check cadence. --- src/harness/fourslash.ts | 75 ++++++++++++++++++---------------------- src/harness/harness.ts | 23 ++++++++++++ 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 06b2af62266..83b6a7a10e1 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1217,7 +1217,7 @@ module FourSlash { ts.forEach(fileNames, Harness.IO.log); } - public deleteChar(count = 1) { + public deleteChar(count = 1, cadence = 10) { this.scenarioActions.push(''); var offset = this.currentCaretPosition; @@ -1227,12 +1227,18 @@ module FourSlash { // Make the edit this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch); - this.checkPostEditInvariants(); + + if (i % cadence === 0) { + this.checkPostEditInvariants(); + } // Handle post-keystroke formatting if (this.enableFormatting) { var edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions); - offset += this.applyEdits(this.activeFile.fileName, edits, true); + if (edits.length) { + offset += this.applyEdits(this.activeFile.fileName, edits, true); + this.checkPostEditInvariants(); + } } } @@ -1249,11 +1255,9 @@ module FourSlash { this.languageServiceShimHost.editScript(this.activeFile.fileName, start, start + length, text); this.updateMarkersForEdit(this.activeFile.fileName, start, start + length, text); this.checkPostEditInvariants(); - - this.checkPostEditInvariants(); } - public deleteCharBehindMarker(count = 1) { + public deleteCharBehindMarker(count = 1, cadence = 10) { this.scenarioActions.push(''); var offset = this.currentCaretPosition; @@ -1264,13 +1268,18 @@ module FourSlash { // Make the edit this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch); - this.checkPostEditInvariants(); + + if (i % cadence === 0) { + this.checkPostEditInvariants(); + } // Handle post-keystroke formatting if (this.enableFormatting) { var edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions); - offset += this.applyEdits(this.activeFile.fileName, edits, true); - this.checkPostEditInvariants(); + if (edits.length) { + offset += this.applyEdits(this.activeFile.fileName, edits, true); + this.checkPostEditInvariants(); + } } } @@ -1295,7 +1304,7 @@ module FourSlash { // Enters lines of text at the current caret position, invoking // language service APIs to mimic Visual Studio's behavior // as much as possible - private typeHighFidelity(text: string, errorCadence = 5) { + private typeHighFidelity(text: string, cadence = 10) { var offset = this.currentCaretPosition; var prevChar = ' '; for (var i = 0; i < text.length; i++) { @@ -1305,7 +1314,6 @@ module FourSlash { this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, offset); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, ch); - this.checkPostEditInvariants(); offset++; if (ch === '(' || ch === ',') { @@ -1316,16 +1324,19 @@ module FourSlash { this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset); } - if (i % errorCadence === 0) { - this.languageService.getSyntacticDiagnostics(this.activeFile.fileName); + if (i % cadence === 0) { + this.checkPostEditInvariants(); + // this.languageService.getSyntacticDiagnostics(this.activeFile.fileName); this.languageService.getSemanticDiagnostics(this.activeFile.fileName); } // Handle post-keystroke formatting if (this.enableFormatting) { var edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions); - offset += this.applyEdits(this.activeFile.fileName, edits, true); - this.checkPostEditInvariants(); + if (edits.length) { + offset += this.applyEdits(this.activeFile.fileName, edits, true); + this.checkPostEditInvariants(); + } } } @@ -1350,8 +1361,10 @@ module FourSlash { // Handle formatting if (this.enableFormatting) { var edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, offset, this.formatCodeOptions); - offset += this.applyEdits(this.activeFile.fileName, edits, true); - this.checkPostEditInvariants(); + if (edits.length) { + offset += this.applyEdits(this.activeFile.fileName, edits, true); + this.checkPostEditInvariants(); + } } // Move the caret to wherever we ended up @@ -1365,7 +1378,7 @@ module FourSlash { var incrementalSourceFile = this.languageService.getSourceFile(this.activeFile.fileName); Utils.assertInvariants(incrementalSourceFile, /*parent:*/ undefined); - var incrementalSyntaxDiagnostics = JSON.stringify(Utils.convertDiagnostics(incrementalSourceFile.getSyntacticDiagnostics())); + var incrementalSyntaxDiagnostics = incrementalSourceFile.getSyntacticDiagnostics(); // Check syntactic structure var snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName); @@ -1373,32 +1386,10 @@ module FourSlash { var referenceSourceFile = ts.createLanguageServiceSourceFile( this.activeFile.fileName, createScriptSnapShot(content), ts.ScriptTarget.Latest, /*version:*/ "0", /*isOpen:*/ false, /*setNodeParents:*/ false); - var referenceSyntaxDiagnostics = JSON.stringify(Utils.convertDiagnostics(referenceSourceFile.getSyntacticDiagnostics())); - - if (incrementalSyntaxDiagnostics !== referenceSyntaxDiagnostics) { - this.raiseError('Mismatched incremental/reference syntactic diagnostics for file ' + this.activeFile.fileName + '.\n=== Incremental diagnostics ===\n' + incrementalSyntaxDiagnostics + '\n=== Reference Diagnostics ===\n' + referenceSyntaxDiagnostics); - } + var referenceSyntaxDiagnostics = referenceSourceFile.getSyntacticDiagnostics(); + Utils.assertDiagnosticsEquals(incrementalSyntaxDiagnostics, referenceSyntaxDiagnostics); Utils.assertStructuralEquals(incrementalSourceFile, referenceSourceFile); - - //if (this.editValidation !== IncrementalEditValidation.SyntacticOnly) { - // var compiler = new TypeScript.TypeScriptCompiler(); - // for (var i = 0; i < this.testData.files.length; i++) { - // snapshot = this.languageServiceShimHost.getScriptSnapshot(this.testData.files[i].fileName); - // compiler.addFile(this.testData.files[i].fileName, TypeScript.ScriptSnapshot.fromString(snapshot.getText(0, snapshot.getLength())), ts.ByteOrderMark.None, 0, true); - // } - - // compiler.addFile('lib.d.ts', TypeScript.ScriptSnapshot.fromString(Harness.Compiler.libTextMinimal), ts.ByteOrderMark.None, 0, true); - - // for (var i = 0; i < this.testData.files.length; i++) { - // var refSemanticErrs = JSON.stringify(compiler.getSemanticDiagnostics(this.testData.files[i].fileName)); - // var incrSemanticErrs = JSON.stringify(this.languageService.getSemanticDiagnostics(this.testData.files[i].fileName)); - - // if (incrSemanticErrs !== refSemanticErrs) { - // this.raiseError('Mismatched incremental/full semantic errors for file ' + this.testData.files[i].fileName + '\n=== Incremental errors ===\n' + incrSemanticErrs + '\n=== Full Errors ===\n' + refSemanticErrs); - // } - // } - //} } private fixCaretPosition() { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 311e2db93ee..065e9e36cd3 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -291,6 +291,29 @@ module Utils { } } + export function assertDiagnosticsEquals(array1: ts.Diagnostic[], array2: ts.Diagnostic[]) { + if (array1 === array2) { + return; + } + + assert(array1, "array1"); + assert(array2, "array2"); + + assert.equal(array1.length, array2.length, "array1.length !== array2.length"); + + for (var i = 0, n = array1.length; i < n; i++) { + var d1 = array1[i]; + var d2 = array2[i]; + + assert.equal(d1.start, d2.start, "d1.start !== d2.start"); + assert.equal(d1.length, d2.length, "d1.length !== d2.length"); + assert.equal(d1.messageText, d2.messageText, "d1.messageText !== d2.messageText"); + assert.equal(d1.category, d2.category, "d1.category !== d2.category"); + assert.equal(d1.code, d2.code, "d1.code !== d2.code"); + assert.equal(d1.isEarly, d2.isEarly, "d1.isEarly !== d2.isEarly"); + } + } + export function assertStructuralEquals(node1: ts.Node, node2: ts.Node) { if (node1 === node2) { return; From c7bb0a5ae6d55239333321cca8fe087703cb4c53 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 01:09:42 -0800 Subject: [PATCH 33/50] Don't store variable statement flags on its variable declaration children. --- src/compiler/binder.ts | 5 +- src/compiler/checker.ts | 58 ++++--- src/compiler/emitter.ts | 40 +++-- src/compiler/parser.ts | 156 ++++++++---------- src/compiler/types.ts | 13 +- src/compiler/utilities.ts | 50 +++++- src/services/breakpoints.ts | 28 ++-- src/services/navigationBar.ts | 2 +- src/services/services.ts | 17 +- src/services/utilities.ts | 2 +- .../VariableDeclaration10_es6.errors.txt | 4 +- .../VariableDeclaration2_es6.errors.txt | 4 +- .../VariableDeclaration3_es6.errors.txt | 4 +- .../VariableDeclaration4_es6.errors.txt | 4 +- .../VariableDeclaration5_es6.errors.txt | 4 +- .../VariableDeclaration7_es6.errors.txt | 4 +- .../VariableDeclaration8_es6.errors.txt | 4 +- .../VariableDeclaration9_es6.errors.txt | 4 +- .../baselines/reference/bpSpan_const.baseline | 18 +- tests/baselines/reference/bpSpan_let.baseline | 6 +- .../reference/bpSpan_module.baseline | 18 +- .../reference/bpSpan_variables.baseline | 19 +-- .../constDeclarations-es5.errors.txt | 12 +- .../letDeclarations-es5-1.errors.txt | 24 +-- .../reference/letDeclarations-es5.errors.txt | 32 ++-- tests/baselines/reference/promiseChaining.js | 2 +- tests/baselines/reference/promiseChaining1.js | 2 +- ...fierDefinitionLocations_varDeclarations.ts | 2 +- tests/cases/fourslash/navigateItemsLet.ts | 10 +- .../fourslash/navigationItemsExactMatch2.ts | 2 +- .../fourslash/quickInfoDisplayPartsConst.ts | 1 + 31 files changed, 290 insertions(+), 261 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9af347e59bd..e699d094a06 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -206,7 +206,8 @@ module ts { if (symbolKind & SymbolFlags.Namespace) { exportKind |= SymbolFlags.ExportNamespace; } - if (node.flags & NodeFlags.Export || (node.kind !== SyntaxKind.ImportDeclaration && isAmbientContext(container))) { + + if (getNodeFlags(node) & NodeFlags.Export || (node.kind !== SyntaxKind.ImportDeclaration && isAmbientContext(container))) { if (exportKind) { var local = declareSymbol(container.locals, undefined, node, exportKind, symbolExcludes); local.exportSymbol = declareSymbol(container.symbol.exports, container.symbol, node, symbolKind, symbolExcludes); @@ -391,7 +392,7 @@ module ts { if (isBindingPattern((node).name)) { bindChildren(node, 0, /*isBlockScopeContainer*/ false); } - else if (node.flags & NodeFlags.BlockScoped) { + else if (getNodeFlags(node) & NodeFlags.BlockScoped) { bindBlockScopedVariableDeclaration(node); } else { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b725305d4cd..6f39161d161 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16,7 +16,6 @@ module ts { /// If fullTypeCheck === false, the typechecker can take shortcuts and skip checks that only produce errors. /// NOTE: checks that somehow affect decisions being made during typechecking should be executed in both cases. export function createTypeChecker(program: Program, fullTypeCheck: boolean): TypeChecker { - var Symbol = objectAllocator.getSymbolConstructor(); var Type = objectAllocator.getTypeConstructor(); var Signature = objectAllocator.getSignatureConstructor(); @@ -407,7 +406,7 @@ module ts { } if (result.flags & SymbolFlags.BlockScopedVariable) { // Block-scoped variables cannot be used before their definition - var declaration = forEach(result.declarations, d => d.flags & NodeFlags.BlockScoped ? d : undefined); + var declaration = forEach(result.declarations, d => getNodeFlags(d) & NodeFlags.BlockScoped ? d : undefined); Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined"); if (!isDefinedBefore(declaration, errorLocation)) { error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name)); @@ -1557,7 +1556,7 @@ module ts { case SyntaxKind.ImportDeclaration: var parent = getDeclarationContainer(node); // If the node is not exported or it is not ambient module element (except import declaration) - if (!(node.flags & NodeFlags.Export) && + if (!(getNodeFlags(node) & NodeFlags.Export) && !(node.kind !== SyntaxKind.ImportDeclaration && parent.kind !== SyntaxKind.SourceFile && isInAmbientContext(parent))) { return isGlobalSourceFile(parent) || isUsedInExportAssignment(node); } @@ -1620,7 +1619,7 @@ module ts { function getDeclarationContainer(node: Node): Node { node = getRootDeclaration(node); - return node.kind === SyntaxKind.VariableDeclaration ? node.parent.parent : node.parent; + return node.kind === SyntaxKind.VariableDeclaration ? node.parent.parent.parent : node.parent; } function getTypeOfPrototypeProperty(prototype: Symbol): Type { @@ -1687,7 +1686,7 @@ module ts { // Return the inferred type for a variable, parameter, or property declaration function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type { // A variable declared in a for..in statement is always of type any - if (declaration.parent.kind === SyntaxKind.ForInStatement) { + if (declaration.parent.parent.kind === SyntaxKind.ForInStatement) { return anyType; } if (isBindingPattern(declaration.parent)) { @@ -5385,7 +5384,7 @@ module ts { } function getDeclarationFlagsFromSymbol(s: Symbol) { - return s.valueDeclaration ? s.valueDeclaration.flags : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0; + return s.valueDeclaration ? getNodeFlags(s.valueDeclaration) : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0; } function checkClassPropertyAccess(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, type: Type, prop: Symbol) { @@ -7271,7 +7270,7 @@ module ts { } function getEffectiveDeclarationFlags(n: Node, flagsToCheck: NodeFlags) { - var flags = n.flags; + var flags = getNodeFlags(n); if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && isInAmbientContext(n)) { if (!(flags & NodeFlags.Ambient)) { // It is nested in an ambient context, which means it is automatically exported @@ -7747,7 +7746,7 @@ module ts { // const x = 0; // var x = 0; // } - if (node.initializer && (node.flags & NodeFlags.BlockScoped) === 0) { + if (node.initializer && (getNodeFlags(node) & NodeFlags.BlockScoped) === 0) { var symbol = getSymbolOfNode(node); if (symbol.flags & SymbolFlags.FunctionScopedVariable) { var localDeclarationSymbol = resolveName(node, (node.name).text, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined); @@ -7799,6 +7798,10 @@ module ts { } } + function checkVariableDeclaration(node: VariableDeclaration) { + return checkVariableLikeDeclaration(node); + } + // Check variable, parameter, or property declaration function checkVariableLikeDeclaration(node: VariableLikeDeclaration) { checkSourceElement(node.type); @@ -7857,7 +7860,7 @@ module ts { } function checkVariableStatement(node: VariableStatement) { - forEach(node.declarations, checkSourceElement); + forEach(node.declarationList.declarations, checkSourceElement); } function checkExpressionStatement(node: ExpressionStatement) { @@ -7881,8 +7884,15 @@ module ts { } function checkForStatement(node: ForStatement) { - if (node.declarations) forEach(node.declarations, checkVariableLikeDeclaration); - if (node.initializer) checkExpression(node.initializer); + if (node.initializer) { + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + forEach((node.initializer).declarations, checkVariableDeclaration) + } + else { + checkExpression(node.initializer) + } + } + if (node.condition) checkExpression(node.condition); if (node.iterator) checkExpression(node.iterator); checkSourceElement(node.statement); @@ -7895,28 +7905,29 @@ module ts { // for (var VarDecl in Expr) Statement // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, // and Expr must be an expression of type Any, an object type, or a type parameter type. - if (node.declarations) { - if (node.declarations.length >= 1) { - var decl = node.declarations[0]; + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + var variableDeclarationList = node.initializer; + if (variableDeclarationList.declarations.length >= 1) { + var decl = variableDeclarationList.declarations[0]; checkVariableLikeDeclaration(decl); if (decl.type) { error(decl, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation); } } } - - // In a 'for-in' statement of the form - // for (Var in Expr) Statement - // Var must be an expression classified as a reference of type Any or the String primitive type, - // and Expr must be an expression of type Any, an object type, or a type parameter type. - if (node.variable) { - var exprType = checkExpression(node.variable); + else { + // In a 'for-in' statement of the form + // for (Var in Expr) Statement + // Var must be an expression classified as a reference of type Any or the String primitive type, + // and Expr must be an expression of type Any, an object type, or a type parameter type. + var varExpr = node.initializer; + var exprType = checkExpression(varExpr); if (exprType !== anyType && exprType !== stringType) { - error(node.variable, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); + error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); } else { // run check only former check succeeded to avoid cascading errors - checkReferenceExpression(node.variable, Diagnostics.Invalid_left_hand_side_in_for_in_statement, Diagnostics.Left_hand_side_of_assignment_expression_cannot_be_a_constant); + checkReferenceExpression(varExpr, Diagnostics.Invalid_left_hand_side_in_for_in_statement, Diagnostics.Left_hand_side_of_assignment_expression_cannot_be_a_constant); } } @@ -8844,6 +8855,7 @@ module ts { case SyntaxKind.TryStatement: case SyntaxKind.CatchClause: case SyntaxKind.VariableDeclaration: + case SyntaxKind.VariableDeclarationList: case SyntaxKind.ClassDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.EnumMember: diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 2d0f06e6565..5e3290120b6 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1014,20 +1014,20 @@ module ts { } function emitVariableStatement(node: VariableStatement) { - var hasDeclarationWithEmit = forEach(node.declarations, varDeclaration => resolver.isDeclarationVisible(varDeclaration)); + var hasDeclarationWithEmit = forEach(node.declarationList.declarations, varDeclaration => resolver.isDeclarationVisible(varDeclaration)); if (hasDeclarationWithEmit) { emitJsDocComments(node); emitModuleElementDeclarationFlags(node); - if (isLet(node)) { + if (isLet(node.declarationList)) { write("let "); } - else if (isConst(node)) { + else if (isConst(node.declarationList)) { write("const "); } else { write("var "); } - emitCommaList(node.declarations, emitVariableDeclaration); + emitCommaList(node.declarationList.declarations, emitVariableDeclaration); write(";"); writeLine(); } @@ -2610,20 +2610,22 @@ module ts { var endPos = emitToken(SyntaxKind.ForKeyword, node.pos); write(" "); endPos = emitToken(SyntaxKind.OpenParenToken, endPos); - if (node.declarations) { - if (node.declarations[0] && isLet(node.declarations[0])) { + if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { + var variableDeclarationList = node.initializer; + var declarations = variableDeclarationList.declarations; + if (declarations[0] && isLet(declarations[0])) { emitToken(SyntaxKind.LetKeyword, endPos); } - else if (node.declarations[0] && isConst(node.declarations[0])) { + else if (declarations[0] && isConst(declarations[0])) { emitToken(SyntaxKind.ConstKeyword, endPos); } else { emitToken(SyntaxKind.VarKeyword, endPos); } write(" "); - emitCommaList(node.declarations); + emitCommaList(variableDeclarationList.declarations); } - if (node.initializer) { + else if (node.initializer) { emit(node.initializer); } write(";"); @@ -2638,9 +2640,10 @@ module ts { var endPos = emitToken(SyntaxKind.ForKeyword, node.pos); write(" "); endPos = emitToken(SyntaxKind.OpenParenToken, endPos); - if (node.declarations) { - if (node.declarations.length >= 1) { - var decl = node.declarations[0]; + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + var variableDeclarationList = node.initializer; + if (variableDeclarationList.declarations.length >= 1) { + var decl = variableDeclarationList.declarations[0]; if (isLet(decl)) { emitToken(SyntaxKind.LetKeyword, endPos); } @@ -2652,7 +2655,7 @@ module ts { } } else { - emit(node.variable); + emit(node.initializer); } write(" in "); emit(node.expression); @@ -2769,7 +2772,7 @@ module ts { function emitModuleMemberName(node: Declaration) { emitStart(node.name); - if (node.flags & NodeFlags.Export) { + if (getNodeFlags(node) & NodeFlags.Export) { var container = getContainingModule(node); write(container ? resolver.getLocalNameOfContainer(container) : "exports"); write("."); @@ -2782,7 +2785,7 @@ module ts { var emitCount = 0; // An exported declaration is actually emitted as an assignment (to a property on the module object), so // temporary variables in an exported declaration need to have real declarations elsewhere - var isDeclaration = (root.kind === SyntaxKind.VariableDeclaration && !(root.flags & NodeFlags.Export)) || root.kind === SyntaxKind.Parameter; + var isDeclaration = (root.kind === SyntaxKind.VariableDeclaration && !(getNodeFlags(root) & NodeFlags.Export)) || root.kind === SyntaxKind.Parameter; if (root.kind === SyntaxKind.BinaryExpression) { emitAssignmentExpression(root); } @@ -2991,17 +2994,17 @@ module ts { function emitVariableStatement(node: VariableStatement) { emitLeadingComments(node); if (!(node.flags & NodeFlags.Export)) { - if (isLet(node)) { + if (isLet(node.declarationList)) { write("let "); } - else if (isConst(node)) { + else if (isConst(node.declarationList)) { write("const "); } else { write("var "); } } - emitCommaList(node.declarations); + emitCommaList(node.declarationList.declarations); write(";"); emitTrailingComments(node); } @@ -3818,6 +3821,7 @@ module ts { if (!node) { return; } + if (node.flags & NodeFlags.Ambient) { return emitPinnedOrTripleSlashComments(node); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ecdabe262d8..899b07fed48 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -158,7 +158,9 @@ module ts { child((node).endOfFileToken); case SyntaxKind.VariableStatement: return children(node.modifiers) || - children((node).declarations); + child((node).declarationList); + case SyntaxKind.VariableDeclarationList: + return children((node).declarations); case SyntaxKind.ExpressionStatement: return child((node).expression); case SyntaxKind.IfStatement: @@ -172,14 +174,12 @@ module ts { return child((node).expression) || child((node).statement); case SyntaxKind.ForStatement: - return children((node).declarations) || - child((node).initializer) || + return child((node).initializer) || child((node).condition) || child((node).iterator) || child((node).statement); case SyntaxKind.ForInStatement: - return children((node).declarations) || - child((node).variable) || + return child((node).initializer) || child((node).expression) || child((node).statement); case SyntaxKind.ContinueStatement: @@ -2992,42 +2992,27 @@ module ts { var pos = getNodePos(); parseExpected(SyntaxKind.ForKeyword); parseExpected(SyntaxKind.OpenParenToken); + + var initializer: VariableDeclarationList | Expression = undefined; if (token !== SyntaxKind.SemicolonToken) { - if (parseOptional(SyntaxKind.VarKeyword)) { - var declarations = disallowInAnd(parseVariableDeclarationList); - } - else if (parseOptional(SyntaxKind.LetKeyword)) { - var declarations = setFlag(disallowInAnd(parseVariableDeclarationList), NodeFlags.Let); - } - else if (parseOptional(SyntaxKind.ConstKeyword)) { - var declarations = setFlag(disallowInAnd(parseVariableDeclarationList), NodeFlags.Const); + if (token === SyntaxKind.VarKeyword || token === SyntaxKind.LetKeyword || token === SyntaxKind.ConstKeyword) { + initializer = parseVariableDeclarationList(/*disallowIn:*/ true); } else { - var varOrInit = disallowInAnd(parseExpression); + initializer = disallowInAnd(parseExpression); } } var forOrForInStatement: IterationStatement; if (parseOptional(SyntaxKind.InKeyword)) { var forInStatement = createNode(SyntaxKind.ForInStatement, pos); - if (declarations) { - forInStatement.declarations = declarations; - } - else { - forInStatement.variable = varOrInit; - } + forInStatement.initializer = initializer; forInStatement.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); forOrForInStatement = forInStatement; } else { var forStatement = createNode(SyntaxKind.ForStatement, pos); - if (declarations) { - forStatement.declarations = declarations; - } - if (varOrInit) { - forStatement.initializer = varOrInit; - } - + forStatement.initializer = initializer; parseExpected(SyntaxKind.SemicolonToken); if (token !== SyntaxKind.SemicolonToken && token !== SyntaxKind.CloseParenToken) { forStatement.condition = allowInAnd(parseExpression); @@ -3424,39 +3409,37 @@ module ts { return finishNode(node); } - function setFlag(nodes: NodeArray, flag: NodeFlags): NodeArray { - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i]; - node.flags |= flag; - if (node.name && isBindingPattern(node.name)) { - setFlag((node.name).elements, flag); - } - } - return nodes; - } + function parseVariableDeclarationList(disallowIn: boolean): VariableDeclarationList { + var node = createNode(SyntaxKind.VariableDeclarationList); - function parseVariableDeclarationList(): NodeArray { - return parseDelimitedList(ParsingContext.VariableDeclarations, parseVariableDeclaration); + switch (token) { + case SyntaxKind.VarKeyword: + break; + case SyntaxKind.LetKeyword: + node.flags |= NodeFlags.Let; + break; + case SyntaxKind.ConstKeyword: + node.flags |= NodeFlags.Const; + break; + default: + Debug.fail(); + } + + nextToken(); + var savedDisallowIn = inDisallowInContext(); + setDisallowInContext(disallowIn); + + node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations, parseVariableDeclaration); + + setDisallowInContext(savedDisallowIn); + + return finishNode(node); } function parseVariableStatement(fullStart: number, modifiers: ModifiersArray): VariableStatement { var node = createNode(SyntaxKind.VariableStatement, fullStart); setModifiers(node, modifiers); - - if (token === SyntaxKind.LetKeyword) { - node.flags |= NodeFlags.Let; - } - else if (token === SyntaxKind.ConstKeyword) { - node.flags |= NodeFlags.Const; - } - else { - Debug.assert(token === SyntaxKind.VarKeyword); - } - - nextToken(); - node.declarations = allowInAnd(parseVariableDeclarationList); - setFlag(node.declarations, node.flags); - + node.declarationList = parseVariableDeclarationList(/*disallowIn:*/ false); parseSemicolon(); return finishNode(node); } @@ -4010,12 +3993,14 @@ module ts { } function getExternalModuleIndicator() { - return forEach(sourceFile.statements, node => - node.flags & NodeFlags.Export - || node.kind === SyntaxKind.ImportDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference - || node.kind === SyntaxKind.ExportAssignment - ? node - : undefined); + return forEach(sourceFile.statements, node => { + var flags = node.flags; + return flags & NodeFlags.Export + || node.kind === SyntaxKind.ImportDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference + || node.kind === SyntaxKind.ExportAssignment + ? node + : undefined; + }); } var syntacticDiagnostics: Diagnostic[]; @@ -4166,8 +4151,6 @@ module ts { case SyntaxKind.ElementAccessExpression: return checkElementAccessExpression(node); case SyntaxKind.ExportAssignment: return checkExportAssignment(node); case SyntaxKind.ExternalModuleReference: return checkExternalModuleReference(node); - case SyntaxKind.ForInStatement: return checkForInStatement(node); - case SyntaxKind.ForStatement: return checkForStatement(node); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.FunctionExpression: return checkFunctionExpression(node); case SyntaxKind.GetAccessor: return checkGetAccessor(node); @@ -4199,6 +4182,7 @@ module ts { case SyntaxKind.TypeParameter: return checkTypeParameter(node); case SyntaxKind.TypeReference: return checkTypeReference(node); case SyntaxKind.VariableDeclaration: return checkVariableDeclaration(node); + case SyntaxKind.VariableDeclarationList: return checkVariableDeclarationList(node); case SyntaxKind.VariableStatement: return checkVariableStatement(node); case SyntaxKind.WithStatement: return checkWithStatement(node); case SyntaxKind.YieldExpression: return checkYieldExpression(node); @@ -4561,15 +4545,6 @@ module ts { } } - function checkForInStatement(node: ForInStatement) { - return checkVariableDeclarations(node.declarations) || - checkForMoreThanOneDeclaration(node.declarations); - } - - function checkForStatement(node: ForStatement) { - return checkVariableDeclarations(node.declarations); - } - function checkForMoreThanOneDeclaration(declarations: NodeArray) { if (declarations && declarations.length > 1) { return grammarErrorOnFirstToken(declarations[1], Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement); @@ -5306,31 +5281,32 @@ module ts { } } - function checkVariableDeclarations(declarations: NodeArray): boolean { - if (declarations) { - if (checkForDisallowedTrailingComma(declarations)) { - return true; - } + function checkVariableDeclarationList(declarationList: VariableDeclarationList): boolean { + var declarations = declarationList.declarations; + if (checkForDisallowedTrailingComma(declarationList.declarations)) { + return true; + } - if (!declarations.length) { - return grammarErrorAtPos(declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); - } + if (!declarationList.declarations.length) { + return grammarErrorAtPos(declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); + } - var decl = declarations[0]; - if (languageVersion < ScriptTarget.ES6) { - if (isLet(decl)) { - return grammarErrorOnFirstToken(decl, Diagnostics.let_declarations_are_only_available_when_targeting_ECMAScript_6_and_higher); - } - else if (isConst(decl)) { - return grammarErrorOnFirstToken(decl, Diagnostics.const_declarations_are_only_available_when_targeting_ECMAScript_6_and_higher); - } + if (declarationList.parent.kind === SyntaxKind.ForInStatement) { + checkForMoreThanOneDeclaration(declarationList.declarations); + } + + if (languageVersion < ScriptTarget.ES6) { + if (isLet(declarationList)) { + return grammarErrorOnFirstToken(declarationList, Diagnostics.let_declarations_are_only_available_when_targeting_ECMAScript_6_and_higher); + } + else if (isConst(declarationList)) { + return grammarErrorOnFirstToken(declarationList, Diagnostics.const_declarations_are_only_available_when_targeting_ECMAScript_6_and_higher); } } } function checkVariableStatement(node: VariableStatement) { return checkForDisallowedModifiersInBlockOrObjectLiteral(node) || - checkVariableDeclarations(node.declarations) || checkForDisallowedLetOrConstStatement(node); } @@ -5344,10 +5320,10 @@ module ts { function checkForDisallowedLetOrConstStatement(node: VariableStatement) { if (!allowLetAndConstDeclarations(node.parent)) { - if (isLet(node)) { + if (isLet(node.declarationList)) { return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block); } - else if (isConst(node)) { + else if (isConst(node.declarationList)) { return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7dc1b38b30d..c5bb45f04a5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -219,6 +219,7 @@ module ts { TryStatement, DebuggerStatement, VariableDeclaration, + VariableDeclarationList, FunctionDeclaration, ClassDeclaration, InterfaceDeclaration, @@ -398,6 +399,10 @@ module ts { initializer?: Expression; // Optional initializer } + export interface VariableDeclarationList extends Node { + declarations: NodeArray; + } + // SyntaxKind.Parameter export interface ParameterDeclaration extends Declaration { dotDotDotToken?: Node; // Present on rest parameter @@ -702,7 +707,7 @@ module ts { } export interface VariableStatement extends Statement { - declarations: NodeArray; + declarationList: VariableDeclarationList; } export interface ExpressionStatement extends Statement { @@ -728,15 +733,13 @@ module ts { } export interface ForStatement extends IterationStatement { - declarations?: NodeArray; - initializer?: Expression; + initializer?: VariableDeclarationList | Expression; condition?: Expression; iterator?: Expression; } export interface ForInStatement extends IterationStatement { - declarations?: NodeArray; - variable?: Expression; + initializer: VariableDeclarationList | Expression; expression: Expression; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index cfa8004aa38..f23af94f7aa 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -220,12 +220,40 @@ module ts { 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; + } + + export function getNodeFlags(node: Node): NodeFlags { + node = walkUpBindingElementsAndPatterns(node); + + var 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 !!(node.flags & NodeFlags.Const); + return !!(getNodeFlags(node) & NodeFlags.Const); } export function isLet(node: Node): boolean { - return !!(node.flags & NodeFlags.Let); + return !!(getNodeFlags(node) & NodeFlags.Let); } export function isPrologueDirective(node: Node): boolean { @@ -456,12 +484,14 @@ module ts { case SyntaxKind.SwitchStatement: return (parent).expression === node; case SyntaxKind.ForStatement: - return (parent).initializer === node || - (parent).condition === node || - (parent).iterator === node; + var forStatement = parent; + return (forStatement.initializer === node && forStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || + forStatement.condition === node || + forStatement.iterator === node; case SyntaxKind.ForInStatement: - return (parent).variable === node || - (parent).expression === node; + var forInStatement = parent; + return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || + forInStatement.expression === node; case SyntaxKind.TypeAssertionExpression: return node === (parent).expression; case SyntaxKind.TemplateSpan: @@ -533,7 +563,10 @@ module ts { export function isInAmbientContext(node: Node): boolean { while (node) { - if (node.flags & (NodeFlags.Ambient | NodeFlags.DeclarationFile)) return true; + if (node.flags & (NodeFlags.Ambient | NodeFlags.DeclarationFile)) { + return true; + } + node = node.parent; } return false; @@ -735,5 +768,4 @@ module ts { } return false; } - } \ No newline at end of file diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 8c3dda08d22..83c442baa39 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -83,7 +83,7 @@ module ts.BreakpointResolver { switch (node.kind) { case SyntaxKind.VariableStatement: // Span on first variable declaration - return spanInVariableDeclaration((node).declarations[0]); + return spanInVariableDeclaration((node).declarationList.declarations[0]); case SyntaxKind.VariableDeclaration: case SyntaxKind.PropertyDeclaration: @@ -261,16 +261,16 @@ module ts.BreakpointResolver { function spanInVariableDeclaration(variableDeclaration: VariableDeclaration): TextSpan { // If declaration of for in statement, just set the span in parent - if (variableDeclaration.parent.kind === SyntaxKind.ForInStatement) { - return spanInNode(variableDeclaration.parent); + if (variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) { + return spanInNode(variableDeclaration.parent.parent); } - var isParentVariableStatement = variableDeclaration.parent.kind === SyntaxKind.VariableStatement; - var isDeclarationOfForStatement = variableDeclaration.parent.kind === SyntaxKind.ForStatement && contains((variableDeclaration.parent).declarations, variableDeclaration); + var isParentVariableStatement = variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement; + var isDeclarationOfForStatement = variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement && contains(((variableDeclaration.parent.parent).initializer).declarations, variableDeclaration); var declarations = isParentVariableStatement - ? (variableDeclaration.parent).declarations + ? (variableDeclaration.parent.parent).declarationList.declarations : isDeclarationOfForStatement - ? (variableDeclaration.parent).declarations + ? ((variableDeclaration.parent.parent).initializer).declarations : undefined; // Breakpoint is possible in variableDeclaration only if there is initialization @@ -374,12 +374,18 @@ module ts.BreakpointResolver { } function spanInForStatement(forStatement: ForStatement): TextSpan { - if (forStatement.declarations) { - return spanInNode(forStatement.declarations[0]); - } if (forStatement.initializer) { - return spanInNode(forStatement.initializer); + if (forStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { + var variableDeclarationList = forStatement.initializer; + if (variableDeclarationList.declarations.length > 0) { + return spanInNode(variableDeclarationList.declarations[0]); + } + } + else { + return spanInNode(forStatement.initializer); + } } + if (forStatement.condition) { return textSpan(forStatement.condition); } diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 29b2db72fc5..e5a7d6b6402 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -44,7 +44,7 @@ module ts.NavigationBar { function visit(node: Node) { switch (node.kind) { case SyntaxKind.VariableStatement: - forEach((node).declarations, visit); + forEach((node).declarationList.declarations, visit); break; case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: diff --git a/src/services/services.ts b/src/services/services.ts index cc1eb552d2c..25e7548a0d6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -365,7 +365,7 @@ module ts { // Get the cleaned js doc comment text from the declaration ts.forEach(getJsDocCommentTextRange( - declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent : declaration, sourceFileOfDeclaration), jsDocCommentTextRange => { + declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration, sourceFileOfDeclaration), jsDocCommentTextRange => { var cleanedJsDocComment = getCleanedJsDocComment(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration); if (cleanedJsDocComment) { jsDocCommentParts.push.apply(jsDocCommentParts, cleanedJsDocComment); @@ -811,6 +811,7 @@ module ts { // fall through case SyntaxKind.Constructor: case SyntaxKind.VariableStatement: + case SyntaxKind.VariableDeclarationList: case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ModuleBlock: @@ -2733,6 +2734,7 @@ module ts { switch (previousToken.kind) { case SyntaxKind.CommaToken: return containingNodeKind === SyntaxKind.VariableDeclaration || + containingNodeKind === SyntaxKind.VariableDeclarationList || containingNodeKind === SyntaxKind.VariableStatement || containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, | isFunction(containingNodeKind); @@ -2926,7 +2928,7 @@ module ts { else if (symbol.valueDeclaration && isConst(symbol.valueDeclaration)) { return ScriptElementKind.constElement; } - else if (forEach(symbol.declarations, declaration => isLet(declaration))) { + else if (forEach(symbol.declarations, isLet)) { return ScriptElementKind.letElement; } return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement; @@ -2984,11 +2986,12 @@ module ts { case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement; case SyntaxKind.TypeAliasDeclaration: return ScriptElementKind.typeElement; case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; - case SyntaxKind.VariableDeclaration: return isConst(node) - ? ScriptElementKind.constElement - : node.flags & NodeFlags.Let - ? ScriptElementKind.letElement - : ScriptElementKind.variableElement; + case SyntaxKind.VariableDeclaration: + return isConst(node) + ? ScriptElementKind.constElement + : isLet(node) + ? ScriptElementKind.letElement + : ScriptElementKind.variableElement; case SyntaxKind.FunctionDeclaration: return ScriptElementKind.functionElement; case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 146ea141d76..1b7fa1bea64 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -271,7 +271,7 @@ module ts { } export function getNodeModifiers(node: Node): string { - var flags = node.flags; + var flags = getNodeFlags(node); var result: string[] = []; if (flags & NodeFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); diff --git a/tests/baselines/reference/VariableDeclaration10_es6.errors.txt b/tests/baselines/reference/VariableDeclaration10_es6.errors.txt index 91c071903c4..b7d4bf2f1c3 100644 --- a/tests/baselines/reference/VariableDeclaration10_es6.errors.txt +++ b/tests/baselines/reference/VariableDeclaration10_es6.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/es6/variableDeclarations/VariableDeclaration10_es6.ts(1,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/conformance/es6/variableDeclarations/VariableDeclaration10_es6.ts(1,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/conformance/es6/variableDeclarations/VariableDeclaration10_es6.ts (1 errors) ==== let a: number = 1 - ~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/VariableDeclaration2_es6.errors.txt b/tests/baselines/reference/VariableDeclaration2_es6.errors.txt index 362b68dbb35..98c5ea9fce8 100644 --- a/tests/baselines/reference/VariableDeclaration2_es6.errors.txt +++ b/tests/baselines/reference/VariableDeclaration2_es6.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/es6/variableDeclarations/VariableDeclaration2_es6.ts(1,7): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/conformance/es6/variableDeclarations/VariableDeclaration2_es6.ts(1,1): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/conformance/es6/variableDeclarations/VariableDeclaration2_es6.ts (1 errors) ==== const a - ~ + ~~~~~ !!! error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/VariableDeclaration3_es6.errors.txt b/tests/baselines/reference/VariableDeclaration3_es6.errors.txt index e563c4f3ea7..a3b09f70d2d 100644 --- a/tests/baselines/reference/VariableDeclaration3_es6.errors.txt +++ b/tests/baselines/reference/VariableDeclaration3_es6.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/es6/variableDeclarations/VariableDeclaration3_es6.ts(1,7): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/conformance/es6/variableDeclarations/VariableDeclaration3_es6.ts(1,1): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/conformance/es6/variableDeclarations/VariableDeclaration3_es6.ts (1 errors) ==== const a = 1 - ~ + ~~~~~ !!! error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/VariableDeclaration4_es6.errors.txt b/tests/baselines/reference/VariableDeclaration4_es6.errors.txt index ae035825312..a411877f527 100644 --- a/tests/baselines/reference/VariableDeclaration4_es6.errors.txt +++ b/tests/baselines/reference/VariableDeclaration4_es6.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/es6/variableDeclarations/VariableDeclaration4_es6.ts(1,7): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/conformance/es6/variableDeclarations/VariableDeclaration4_es6.ts(1,1): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/conformance/es6/variableDeclarations/VariableDeclaration4_es6.ts (1 errors) ==== const a: number - ~ + ~~~~~ !!! error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/VariableDeclaration5_es6.errors.txt b/tests/baselines/reference/VariableDeclaration5_es6.errors.txt index e55f5a26544..c72d423e460 100644 --- a/tests/baselines/reference/VariableDeclaration5_es6.errors.txt +++ b/tests/baselines/reference/VariableDeclaration5_es6.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/es6/variableDeclarations/VariableDeclaration5_es6.ts(1,7): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/conformance/es6/variableDeclarations/VariableDeclaration5_es6.ts(1,1): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/conformance/es6/variableDeclarations/VariableDeclaration5_es6.ts (1 errors) ==== const a: number = 1 - ~ + ~~~~~ !!! error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/VariableDeclaration7_es6.errors.txt b/tests/baselines/reference/VariableDeclaration7_es6.errors.txt index 5de31099e5b..83d12d463e6 100644 --- a/tests/baselines/reference/VariableDeclaration7_es6.errors.txt +++ b/tests/baselines/reference/VariableDeclaration7_es6.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/es6/variableDeclarations/VariableDeclaration7_es6.ts(1,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/conformance/es6/variableDeclarations/VariableDeclaration7_es6.ts(1,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/conformance/es6/variableDeclarations/VariableDeclaration7_es6.ts (1 errors) ==== let a - ~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/VariableDeclaration8_es6.errors.txt b/tests/baselines/reference/VariableDeclaration8_es6.errors.txt index 5409c6657c1..e371c64c9a6 100644 --- a/tests/baselines/reference/VariableDeclaration8_es6.errors.txt +++ b/tests/baselines/reference/VariableDeclaration8_es6.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/es6/variableDeclarations/VariableDeclaration8_es6.ts(1,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/conformance/es6/variableDeclarations/VariableDeclaration8_es6.ts(1,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/conformance/es6/variableDeclarations/VariableDeclaration8_es6.ts (1 errors) ==== let a = 1 - ~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/VariableDeclaration9_es6.errors.txt b/tests/baselines/reference/VariableDeclaration9_es6.errors.txt index 0a2a25858bb..4a1890d4694 100644 --- a/tests/baselines/reference/VariableDeclaration9_es6.errors.txt +++ b/tests/baselines/reference/VariableDeclaration9_es6.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/es6/variableDeclarations/VariableDeclaration9_es6.ts(1,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/conformance/es6/variableDeclarations/VariableDeclaration9_es6.ts(1,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/conformance/es6/variableDeclarations/VariableDeclaration9_es6.ts (1 errors) ==== let a: number - ~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/bpSpan_const.baseline b/tests/baselines/reference/bpSpan_const.baseline index bee7d930955..176fb76be86 100644 --- a/tests/baselines/reference/bpSpan_const.baseline +++ b/tests/baselines/reference/bpSpan_const.baseline @@ -76,21 +76,21 @@ -------------------------------- 7 > export const cc1 = false; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (181 to 210) SpanInfo: {"start":185,"length":24} - >export const cc1 = false - >:=> (line 7, col 4) to (line 7, col 28) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (181 to 210) SpanInfo: {"start":192,"length":17} + >const cc1 = false + >:=> (line 7, col 11) to (line 7, col 28) -------------------------------- 8 > export const cc2: number = 23; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (211 to 245) SpanInfo: {"start":215,"length":29} - >export const cc2: number = 23 - >:=> (line 8, col 4) to (line 8, col 33) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (211 to 245) SpanInfo: {"start":222,"length":22} + >const cc2: number = 23 + >:=> (line 8, col 11) to (line 8, col 33) -------------------------------- 9 > export const cc3 = 0, cc4 :string = "", cc5 = null; - ~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (246 to 270) SpanInfo: {"start":250,"length":20} - >export const cc3 = 0 - >:=> (line 9, col 4) to (line 9, col 24) + ~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (246 to 270) SpanInfo: {"start":257,"length":13} + >const cc3 = 0 + >:=> (line 9, col 11) to (line 9, col 24) 9 > export const cc3 = 0, cc4 :string = "", cc5 = null; ~~~~~~~~~~~~~~~~~~ => Pos: (271 to 288) SpanInfo: {"start":272,"length":16} diff --git a/tests/baselines/reference/bpSpan_let.baseline b/tests/baselines/reference/bpSpan_let.baseline index dff12280a96..d9a3c2f8547 100644 --- a/tests/baselines/reference/bpSpan_let.baseline +++ b/tests/baselines/reference/bpSpan_let.baseline @@ -84,9 +84,9 @@ -------------------------------- 11 > export let ll2 = 0; - ~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (221 to 244) SpanInfo: {"start":225,"length":18} - >export let ll2 = 0 - >:=> (line 11, col 4) to (line 11, col 22) + ~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (221 to 244) SpanInfo: {"start":232,"length":11} + >let ll2 = 0 + >:=> (line 11, col 11) to (line 11, col 22) -------------------------------- 12 >} ~ => Pos: (245 to 245) SpanInfo: {"start":245,"length":1} diff --git a/tests/baselines/reference/bpSpan_module.baseline b/tests/baselines/reference/bpSpan_module.baseline index 14145512658..e544fc4c432 100644 --- a/tests/baselines/reference/bpSpan_module.baseline +++ b/tests/baselines/reference/bpSpan_module.baseline @@ -50,9 +50,9 @@ -------------------------------- 7 > export var x = 30; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (67 to 93) SpanInfo: {"start":75,"length":17} - >export var x = 30 - >:=> (line 7, col 8) to (line 7, col 25) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (67 to 93) SpanInfo: {"start":82,"length":10} + >var x = 30 + >:=> (line 7, col 15) to (line 7, col 25) -------------------------------- 8 > } @@ -172,15 +172,15 @@ -------------------------------- 25 > { - ~~~~~~ => Pos: (261 to 266) SpanInfo: {"start":275,"length":17} - >export var x = 30 - >:=> (line 26, col 8) to (line 26, col 25) + ~~~~~~ => Pos: (261 to 266) SpanInfo: {"start":282,"length":10} + >var x = 30 + >:=> (line 26, col 15) to (line 26, col 25) -------------------------------- 26 > export var x = 30; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (267 to 293) SpanInfo: {"start":275,"length":17} - >export var x = 30 - >:=> (line 26, col 8) to (line 26, col 25) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (267 to 293) SpanInfo: {"start":282,"length":10} + >var x = 30 + >:=> (line 26, col 15) to (line 26, col 25) -------------------------------- 27 > } diff --git a/tests/baselines/reference/bpSpan_variables.baseline b/tests/baselines/reference/bpSpan_variables.baseline index 1904c34b835..545118daf8d 100644 --- a/tests/baselines/reference/bpSpan_variables.baseline +++ b/tests/baselines/reference/bpSpan_variables.baseline @@ -58,15 +58,13 @@ -------------------------------- 9 > export var xx1; - ~~~~~~~~~~~~~~~~~~~~ => Pos: (119 to 138) SpanInfo: {"start":123,"length":14} - >export var xx1 - >:=> (line 9, col 4) to (line 9, col 18) + ~~~~~~~~~~~~~~~~~~~~ => Pos: (119 to 138) SpanInfo: undefined -------------------------------- 10 > export var xx2 = 10, xx3 = 10; - ~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (139 to 162) SpanInfo: {"start":143,"length":19} - >export var xx2 = 10 - >:=> (line 10, col 4) to (line 10, col 23) + ~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (139 to 162) SpanInfo: {"start":150,"length":12} + >var xx2 = 10 + >:=> (line 10, col 11) to (line 10, col 23) 10 > export var xx2 = 10, xx3 = 10; ~~~~~~~~~~~ => Pos: (163 to 173) SpanInfo: {"start":164,"length":8} @@ -75,14 +73,7 @@ -------------------------------- 11 > export var xx4, xx5; - ~~~~~~~~~~~~~~~~~~~ => Pos: (174 to 192) SpanInfo: {"start":178,"length":14} - >export var xx4 - >:=> (line 11, col 4) to (line 11, col 18) -11 > export var xx4, xx5; - - ~~~~~~ => Pos: (193 to 198) SpanInfo: {"start":194,"length":3} - >xx5 - >:=> (line 11, col 20) to (line 11, col 23) + ~~~~~~~~~~~~~~~~~~~~~~~~~ => Pos: (174 to 198) SpanInfo: undefined -------------------------------- 12 >} ~ => Pos: (199 to 199) SpanInfo: {"start":199,"length":1} diff --git a/tests/baselines/reference/constDeclarations-es5.errors.txt b/tests/baselines/reference/constDeclarations-es5.errors.txt index c928e6ae48e..d15535349ac 100644 --- a/tests/baselines/reference/constDeclarations-es5.errors.txt +++ b/tests/baselines/reference/constDeclarations-es5.errors.txt @@ -1,17 +1,17 @@ -tests/cases/compiler/constDeclarations-es5.ts(2,7): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/constDeclarations-es5.ts(3,7): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/constDeclarations-es5.ts(4,7): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/constDeclarations-es5.ts(2,1): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/constDeclarations-es5.ts(3,1): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/constDeclarations-es5.ts(4,1): error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/compiler/constDeclarations-es5.ts (3 errors) ==== const z7 = false; - ~~ + ~~~~~ !!! error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. const z8: number = 23; - ~~ + ~~~~~ !!! error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. const z9 = 0, z10 :string = "", z11 = null; - ~~ + ~~~~~ !!! error TS1154: 'const' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/letDeclarations-es5-1.errors.txt b/tests/baselines/reference/letDeclarations-es5-1.errors.txt index f0430585a3b..f8b5cf629d2 100644 --- a/tests/baselines/reference/letDeclarations-es5-1.errors.txt +++ b/tests/baselines/reference/letDeclarations-es5-1.errors.txt @@ -1,27 +1,27 @@ -tests/cases/compiler/letDeclarations-es5-1.ts(1,9): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5-1.ts(2,9): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5-1.ts(3,9): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5-1.ts(4,9): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5-1.ts(5,9): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5-1.ts(6,9): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5-1.ts(1,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5-1.ts(2,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5-1.ts(3,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5-1.ts(4,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5-1.ts(5,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5-1.ts(6,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/compiler/letDeclarations-es5-1.ts (6 errors) ==== let l1; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l2: number; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l3, l4, l5 :string, l6; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l7 = false; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l8: number = 23; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l9 = 0, l10 :string = "", l11 = null; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/letDeclarations-es5.errors.txt b/tests/baselines/reference/letDeclarations-es5.errors.txt index 34bd93dba3c..27d526f03ea 100644 --- a/tests/baselines/reference/letDeclarations-es5.errors.txt +++ b/tests/baselines/reference/letDeclarations-es5.errors.txt @@ -1,40 +1,40 @@ -tests/cases/compiler/letDeclarations-es5.ts(2,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5.ts(3,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5.ts(4,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5.ts(6,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5.ts(7,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5.ts(8,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5.ts(10,9): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. -tests/cases/compiler/letDeclarations-es5.ts(12,9): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5.ts(2,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5.ts(3,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5.ts(4,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5.ts(6,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5.ts(7,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5.ts(8,1): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5.ts(10,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. +tests/cases/compiler/letDeclarations-es5.ts(12,5): error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. ==== tests/cases/compiler/letDeclarations-es5.ts (8 errors) ==== let l1; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l2: number; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l3, l4, l5 :string, l6; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l7 = false; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l8: number = 23; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. let l9 = 0, l10 :string = "", l11 = null; - ~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. for(let l11 in {}) { } - ~~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. for(let l12 = 0; l12 < 9; l12++) { } - ~~~ + ~~~ !!! error TS1153: 'let' declarations are only available when targeting ECMAScript 6 and higher. \ No newline at end of file diff --git a/tests/baselines/reference/promiseChaining.js b/tests/baselines/reference/promiseChaining.js index 1d0962d30db..20961542b22 100644 --- a/tests/baselines/reference/promiseChaining.js +++ b/tests/baselines/reference/promiseChaining.js @@ -19,7 +19,7 @@ var Chain = (function () { Chain.prototype.then = function (cb) { var result = cb(this.value); // should get a fresh type parameter which each then call - var z = this.then(function (x) { return result; }).then(function (x) { return "abc"; }).then(function (x) { return x.length; }) /*number*/; // No error + var z = this.then(function (x) { return result; }).then(function (x) { return "abc"; }).then(function (x) { return x.length; }); // No error return new Chain(result); }; return Chain; diff --git a/tests/baselines/reference/promiseChaining1.js b/tests/baselines/reference/promiseChaining1.js index 9dc167f2db6..ee79d3268ba 100644 --- a/tests/baselines/reference/promiseChaining1.js +++ b/tests/baselines/reference/promiseChaining1.js @@ -19,7 +19,7 @@ var Chain2 = (function () { Chain2.prototype.then = function (cb) { var result = cb(this.value); // should get a fresh type parameter which each then call - var z = this.then(function (x) { return result; }).then(function (x) { return "abc"; }).then(function (x) { return x.length; }) /*number*/; // Should error on "abc" because it is not a Function + var z = this.then(function (x) { return result; }).then(function (x) { return "abc"; }).then(function (x) { return x.length; }); // Should error on "abc" because it is not a Function return new Chain2(result); }; return Chain2; diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_varDeclarations.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_varDeclarations.ts index 50a112f4625..346a102faa8 100644 --- a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_varDeclarations.ts +++ b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_varDeclarations.ts @@ -11,7 +11,7 @@ ////var a2, a/*varName4*/ - +debugger; test.markers().forEach((m) => { goTo.position(m.position, m.fileName); verify.completionListIsEmpty(); diff --git a/tests/cases/fourslash/navigateItemsLet.ts b/tests/cases/fourslash/navigateItemsLet.ts index dc399522703..8a82d042738 100644 --- a/tests/cases/fourslash/navigateItemsLet.ts +++ b/tests/cases/fourslash/navigateItemsLet.ts @@ -1,10 +1,10 @@ /// -////{| "itemName": "c", "kind": "let", "parentName": "" |}let c = 10; -////function foo() { -//// {| "itemName": "d", "kind": "let", "parentName": "foo" |}let d = 10; -////} - +////{| "itemName": "c", "kind": "let", "parentName": "" |}let c = 10; +////function foo() { +//// {| "itemName": "d", "kind": "let", "parentName": "foo" |}let d = 10; +////} +debugger; test.markers().forEach(marker => { verify.navigationItemsListContains( marker.data.itemName, diff --git a/tests/cases/fourslash/navigationItemsExactMatch2.ts b/tests/cases/fourslash/navigationItemsExactMatch2.ts index 8f934dcc3c3..adf8e69761e 100644 --- a/tests/cases/fourslash/navigationItemsExactMatch2.ts +++ b/tests/cases/fourslash/navigationItemsExactMatch2.ts @@ -17,7 +17,7 @@ ////function distance2(distanceParam1): void { //// var distanceLocal1; ////} - +debugger; goTo.marker("file1"); verify.navigationItemsListCount(2, "point", "exact"); verify.navigationItemsListCount(5, "distance", "prefix"); diff --git a/tests/cases/fourslash/quickInfoDisplayPartsConst.ts b/tests/cases/fourslash/quickInfoDisplayPartsConst.ts index 62295017dfc..8871a6cc15e 100644 --- a/tests/cases/fourslash/quickInfoDisplayPartsConst.ts +++ b/tests/cases/fourslash/quickInfoDisplayPartsConst.ts @@ -22,6 +22,7 @@ /////*15*/h(10); /////*16*/h("hello"); +debugger; var marker = 0; function verifyConst(name: string, typeDisplay: ts.SymbolDisplayPart[], optionalNameDisplay?: ts.SymbolDisplayPart[], optionalKindModifiers?: string) { marker++; From 592ad476a82f1db516705b6aea96e29c4fe5539b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 02:53:58 -0800 Subject: [PATCH 34/50] Reduce unnecessary arrow function allocations. --- src/compiler/checker.ts | 10 +++++----- src/harness/harness.ts | 2 +- src/services/services.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f39161d161..b04ac3f321c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -763,7 +763,7 @@ module ts { // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) // and if symbolfrom symbolTable or alias resolution matches the symbol, // check the symbol can be qualified, it is only then this symbol is accessible - return !forEach(symbolFromSymbolTable.declarations, declaration => hasExternalModuleSymbol(declaration)) && + return !forEach(symbolFromSymbolTable.declarations, hasExternalModuleSymbol) && canQualifySymbol(symbolFromSymbolTable, meaning); } } @@ -867,7 +867,7 @@ module ts { // This could be a symbol that is not exported in the external module // or it could be a symbol from different external module that is not aliased and hence cannot be named - var symbolExternalModule = forEach(initialSymbol.declarations, declaration => getExternalModuleContainer(declaration)); + var symbolExternalModule = forEach(initialSymbol.declarations, getExternalModuleContainer); if (symbolExternalModule) { var enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration); if (symbolExternalModule !== enclosingExternalModule) { @@ -1087,7 +1087,7 @@ module ts { } else { // If we didn't find accessible symbol chain for this symbol, break if this is external module - if (!parentSymbol && ts.forEach(symbol.declarations, declaration => hasExternalModuleSymbol(declaration))) { + if (!parentSymbol && ts.forEach(symbol.declarations, hasExternalModuleSymbol)) { return; } @@ -4101,7 +4101,7 @@ module ts { return anyType; } if (type.flags & TypeFlags.Union) { - return getUnionType(map((type).types, t => getWidenedType(t))); + return getUnionType(map((type).types, getWidenedType)); } if (isTypeOfObjectLiteral(type)) { return getWidenedTypeOfObjectLiteral(type); @@ -5072,7 +5072,7 @@ module ts { // Return true if the given contextual type is a tuple-like type function contextualTypeIsTupleLikeType(type: Type): boolean { - return !!(type.flags & TypeFlags.Union ? forEach((type).types, t => isTupleLikeType(t)) : isTupleLikeType(type)); + return !!(type.flags & TypeFlags.Union ? forEach((type).types, isTupleLikeType) : isTupleLikeType(type)); } // Return true if the given contextual type provides an index signature of the given kind diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 311e2db93ee..d0276e8b0ce 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1187,7 +1187,7 @@ module Harness { // Report global errors var globalErrors = diagnostics.filter(err => !err.filename); - globalErrors.forEach(err => outputErrorText(err)); + globalErrors.forEach(outputErrorText); // 'merge' the lines of each input file with any errors associated with it inputFiles.filter(f => f.content !== undefined).forEach(inputFile => { diff --git a/src/services/services.ts b/src/services/services.ts index 25e7548a0d6..bf1952c952e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3172,7 +3172,7 @@ module ts { } if (symbolFlags & SymbolFlags.Enum) { addNewLineIfDisplayPartsExist(); - if (forEach(symbol.declarations, declaration => isConstEnumDeclaration(declaration))) { + if (forEach(symbol.declarations, isConstEnumDeclaration)) { displayParts.push(keywordPart(SyntaxKind.ConstKeyword)); displayParts.push(spacePart()); } From 935ba82efd1ac99f766ecdc54d53cd498e55ec85 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 03:08:29 -0800 Subject: [PATCH 35/50] Don't check container invariants. They don't hold true in incremental scenarios. --- src/compiler/binder.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9af347e59bd..128b6f8ffed 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -234,10 +234,8 @@ module ts { parent = node; if (symbolKind & SymbolFlags.IsContainer) { container = node; - Debug.assert(container.nextContainer === undefined); if (lastContainer) { - Debug.assert(lastContainer.nextContainer === undefined); lastContainer.nextContainer = container; } From 0a8744e841a021c4b982d58d5e3947be8b078610 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 03:19:13 -0800 Subject: [PATCH 36/50] Add helper method to reduce so many double negatives in the code. --- src/compiler/checker.ts | 32 ++++++++++++++++---------------- src/compiler/emitter.ts | 4 ++-- src/compiler/parser.ts | 8 ++++---- src/compiler/utilities.ts | 12 ++++++++---- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index de75ae4c244..c4c9af07873 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -649,7 +649,7 @@ module ts { var members = node.members; for (var i = 0; i < members.length; i++) { var member = members[i]; - if (member.kind === SyntaxKind.Constructor && !isMissingNode((member).body)) { + if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member).body)) { return member; } } @@ -2603,7 +2603,7 @@ module ts { returnType = getAnnotatedAccessorType(setter); } - if (!returnType && isMissingNode((declaration).body)) { + if (!returnType && nodeIsMissing((declaration).body)) { returnType = anyType; } } @@ -6354,7 +6354,7 @@ module ts { } // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. - if (isMissingNode(func.body) || func.body.kind !== SyntaxKind.Block) { + if (nodeIsMissing(func.body) || func.body.kind !== SyntaxKind.Block) { return; } @@ -7012,7 +7012,7 @@ module ts { var func = getContainingFunction(node); if (node.flags & (NodeFlags.Public | NodeFlags.Private | NodeFlags.Protected)) { func = getContainingFunction(node); - if (!(func.kind === SyntaxKind.Constructor && !isMissingNode(func.body))) { + if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); } } @@ -7109,7 +7109,7 @@ module ts { } // exit early in the case of signature - super checks are not relevant to them - if (isMissingNode(node.body)) { + if (nodeIsMissing(node.body)) { return; } @@ -7183,7 +7183,7 @@ module ts { function checkAccessorDeclaration(node: AccessorDeclaration) { if (fullTypeCheck) { if (node.kind === SyntaxKind.GetAccessor) { - if (!isInAmbientContext(node) && !isMissingNode(node.body) && !(bodyContainsAReturnStatement(node.body) || bodyContainsSingleThrowStatement(node.body))) { + if (!isInAmbientContext(node) && nodeIsPresent(node.body) && !(bodyContainsAReturnStatement(node.body) || bodyContainsSingleThrowStatement(node.body))) { error(node.name, Diagnostics.A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement); } } @@ -7272,7 +7272,7 @@ module ts { // TypeScript 1.0 spec (April 2014): 3.7.2.2 // Specialized signatures are not permitted in conjunction with a function body - if (!isMissingNode((signatureDeclarationNode).body)) { + if (nodeIsPresent((signatureDeclarationNode).body)) { error(signatureDeclarationNode, Diagnostics.A_signature_with_an_implementation_cannot_use_a_string_literal_type); return; } @@ -7405,7 +7405,7 @@ module ts { error(errorNode, diagnostic); return; } - else if (!isMissingNode((subsequentNode).body)) { + else if (nodeIsPresent((subsequentNode).body)) { error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); return; } @@ -7447,7 +7447,7 @@ module ts { someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); - if (!isMissingNode(node.body) && bodyDeclaration) { + if (nodeIsPresent(node.body) && bodyDeclaration) { if (isConstructor) { multipleConstructorImplementation = true; } @@ -7459,7 +7459,7 @@ module ts { reportImplementationExpectedError(previousDeclaration); } - if (!isMissingNode(node.body)) { + if (nodeIsPresent(node.body)) { if (!bodyDeclaration) { bodyDeclaration = node; } @@ -7642,7 +7642,7 @@ module ts { // Report an implicit any error if there is no body, no explicit return type, and node is not a private method // in an ambient context - if (compilerOptions.noImplicitAny && isMissingNode(node.body) && !node.type && !isPrivateWithinAmbient(node)) { + if (compilerOptions.noImplicitAny && nodeIsMissing(node.body) && !node.type && !isPrivateWithinAmbient(node)) { reportImplicitAnyError(node, anyType); } } @@ -7656,7 +7656,7 @@ module ts { function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { // no rest parameters \ declaration context \ overload - no codegen impact - if (!hasRestParameters(node) || isInAmbientContext(node) || isMissingNode((node).body)) { + if (!hasRestParameters(node) || isInAmbientContext(node) || nodeIsMissing((node).body)) { return; } @@ -7688,7 +7688,7 @@ module ts { } var root = getRootDeclaration(node); - if (root.kind === SyntaxKind.Parameter && isMissingNode((root.parent).body)) { + if (root.kind === SyntaxKind.Parameter && nodeIsMissing((root.parent).body)) { // just an overload - no codegen impact return false; } @@ -7852,7 +7852,7 @@ module ts { forEach((node.name).elements, checkSourceElement); } // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body - if (node.initializer && getRootDeclaration(node).kind === SyntaxKind.Parameter && isMissingNode(getContainingFunction(node).body)) { + if (node.initializer && getRootDeclaration(node).kind === SyntaxKind.Parameter && nodeIsMissing(getContainingFunction(node).body)) { error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); return; } @@ -8606,7 +8606,7 @@ module ts { var declarations = symbol.declarations; for (var i = 0; i < declarations.length; i++) { var declaration = declarations[i]; - if ((declaration.kind === SyntaxKind.ClassDeclaration || (declaration.kind === SyntaxKind.FunctionDeclaration && !isMissingNode((declaration).body))) && !isInAmbientContext(declaration)) { + if ((declaration.kind === SyntaxKind.ClassDeclaration || (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration).body))) && !isInAmbientContext(declaration)) { return declaration; } } @@ -9499,7 +9499,7 @@ module ts { } function isImplementationOfOverload(node: FunctionLikeDeclaration) { - if (!isMissingNode(node.body)) { + if (nodeIsPresent(node.body)) { var symbol = getSymbolOfNode(node); var signaturesOfSymbol = getSignaturesOfSymbol(symbol); // If this function body corresponds to function with multiple signature, it is implementation of overload diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5e3290120b6..6db57460378 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -268,7 +268,7 @@ module ts { function getFirstConstructorWithBody(node: ClassDeclaration): ConstructorDeclaration { return forEach(node.members, member => { - if (member.kind === SyntaxKind.Constructor && !isMissingNode((member).body)) { + if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member).body)) { return member; } }); @@ -3108,7 +3108,7 @@ module ts { } function emitFunctionDeclaration(node: FunctionLikeDeclaration) { - if (isMissingNode(node.body)) { + if (nodeIsMissing(node.body)) { return emitPinnedOrTripleSlashComments(node); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4bd8826e4c6..42a03873ff9 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -974,7 +974,7 @@ module ts { function getLastChildWorker(node: Node): Node { var last:Node = undefined; forEachChild(node, child => { - if (!isMissingNode(child)) { + if (nodeIsPresent(child)) { last = child; } }); @@ -982,7 +982,7 @@ module ts { } function visit(child: Node) { - if (isMissingNode(child)) { + if (nodeIsMissing(child)) { // Missing nodes are effectively invisible to us. We never even consider them // When trying to find the nearest node before us. return; @@ -1675,7 +1675,7 @@ module ts { var node = syntaxCursor.currentNode(scanner.getStartPos()); // Can't reuse a missing node. - if (isMissingNode(node)) { + if (nodeIsMissing(node)) { return undefined; } @@ -5493,7 +5493,7 @@ module ts { if (inAmbientContext) { return checkForDisallowedComputedProperty(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_an_ambient_context); } - else if (isMissingNode(node.body)) { + else if (nodeIsMissing(node.body)) { return checkForDisallowedComputedProperty(node.name, Diagnostics.Computed_property_names_are_not_allowed_in_method_overloads); } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 6137d6e1955..2db5d9e3b7d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -110,7 +110,7 @@ module ts { return node.pos; } - export function isMissingNode(node: Node) { + export function nodeIsMissing(node: Node) { if (!node) { return true; } @@ -118,10 +118,14 @@ module ts { 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 (isMissingNode(node)) { + if (nodeIsMissing(node)) { return node.pos; } @@ -129,7 +133,7 @@ module ts { } export function getSourceTextOfNodeFromSourceFile(sourceFile: SourceFile, node: Node): string { - if (isMissingNode(node)) { + if (nodeIsMissing(node)) { return ""; } @@ -138,7 +142,7 @@ module ts { } export function getTextOfNodeFromSourceText(sourceText: string, node: Node): string { - if (isMissingNode(node)) { + if (nodeIsMissing(node)) { return ""; } From c9ee88e5c4f299266dfdb0a6b8010e5ba598d010 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 11:58:47 -0800 Subject: [PATCH 37/50] Adding incremental test. --- src/harness/fourslash.ts | 24 ++++--- .../deleteModifierBeforeVarStatement1.ts | 64 +++++++++++++++++++ 2 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 tests/cases/fourslash/deleteModifierBeforeVarStatement1.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 83b6a7a10e1..452e52d3ded 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1217,18 +1217,20 @@ module FourSlash { ts.forEach(fileNames, Harness.IO.log); } - public deleteChar(count = 1, cadence = 10) { + public deleteChar(count = 1) { this.scenarioActions.push(''); var offset = this.currentCaretPosition; var ch = ""; + var checkCadence = (count >> 2) + 1 + for (var i = 0; i < count; i++) { // Make the edit this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch); - if (i % cadence === 0) { + if (i % checkCadence === 0) { this.checkPostEditInvariants(); } @@ -1237,7 +1239,7 @@ module FourSlash { var edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions); if (edits.length) { offset += this.applyEdits(this.activeFile.fileName, edits, true); - this.checkPostEditInvariants(); + //this.checkPostEditInvariants(); } } } @@ -1257,11 +1259,12 @@ module FourSlash { this.checkPostEditInvariants(); } - public deleteCharBehindMarker(count = 1, cadence = 10) { + public deleteCharBehindMarker(count = 1) { this.scenarioActions.push(''); var offset = this.currentCaretPosition; var ch = ""; + var checkCadence = (count >> 2) + 1 for (var i = 0; i < count; i++) { offset--; @@ -1269,7 +1272,7 @@ module FourSlash { this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch); - if (i % cadence === 0) { + if (i % checkCadence === 0) { this.checkPostEditInvariants(); } @@ -1278,7 +1281,6 @@ module FourSlash { var edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions); if (edits.length) { offset += this.applyEdits(this.activeFile.fileName, edits, true); - this.checkPostEditInvariants(); } } } @@ -1304,9 +1306,11 @@ module FourSlash { // Enters lines of text at the current caret position, invoking // language service APIs to mimic Visual Studio's behavior // as much as possible - private typeHighFidelity(text: string, cadence = 10) { + private typeHighFidelity(text: string) { var offset = this.currentCaretPosition; var prevChar = ' '; + var checkCadence = (text.length >> 2) + 1; + for (var i = 0; i < text.length; i++) { // Make the edit var ch = text.charAt(i); @@ -1324,10 +1328,10 @@ module FourSlash { this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset); } - if (i % cadence === 0) { + if (i % checkCadence === 0) { this.checkPostEditInvariants(); // this.languageService.getSyntacticDiagnostics(this.activeFile.fileName); - this.languageService.getSemanticDiagnostics(this.activeFile.fileName); + // this.languageService.getSemanticDiagnostics(this.activeFile.fileName); } // Handle post-keystroke formatting @@ -1335,7 +1339,7 @@ module FourSlash { var edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions); if (edits.length) { offset += this.applyEdits(this.activeFile.fileName, edits, true); - this.checkPostEditInvariants(); + // this.checkPostEditInvariants(); } } } diff --git a/tests/cases/fourslash/deleteModifierBeforeVarStatement1.ts b/tests/cases/fourslash/deleteModifierBeforeVarStatement1.ts new file mode 100644 index 00000000000..803d6bf66ce --- /dev/null +++ b/tests/cases/fourslash/deleteModifierBeforeVarStatement1.ts @@ -0,0 +1,64 @@ +/// + +//// +//// +//// ///////////////////////////// +//// /// Windows Script Host APIS +//// ///////////////////////////// +//// +//// declare var ActiveXObject: { new (s: string): any; }; +//// +//// interface ITextWriter { +//// WriteLine(s): void; +//// } +//// +//// declare var WScript: { +//// Echo(s): void; +//// StdErr: ITextWriter; +//// Arguments: { length: number; Item(): string; }; +//// ScriptFullName: string; +//// Quit(): number; +//// } +//// + + +goTo.file(0); + +// : +// : |--- go here +// 1: +// 2: +goTo.position(0); + +// : +// : |--- delete "\n\n///..." +// 1: +// 2: +debugger; +edit.deleteAtCaret(100); + + +// 12: +// : |--- go here +// 13: declare var WScript: { +// 14: Echo(s): void; +goTo.position(198); + +// 12: +// : |--- delete "declare..." +// 13: declare var WScript: { +// 14: Echo(s): void; +edit.deleteAtCaret(16); + + +// 9: StdErr: ITextWriter; +// : |--- go here +// 10: Arguments: { length: number; Item(): string; }; +// 11: ScriptFullName: string; +goTo.position(198); + +// 9: StdErr: ITextWriter; +// : |--- insert "Item(): string; " +// 10: Arguments: { length: number; Item(): string; }; +// 11: ScriptFullName: string; +edit.insert("Item(): string; "); From 97a6abcc07c393bf20669684d49caf2c12efb2bd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 16:12:14 -0800 Subject: [PATCH 38/50] CR feedback. --- src/compiler/parser.ts | 45 ++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 42a03873ff9..45b33fc1bfb 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -426,7 +426,7 @@ module ts { ((node).text === "eval" || (node).text === "arguments"); } - /// Should be called only on prologue directives (isPrologueDirective(node) should be true) + /// Should be called only on prologue directives (isPrologueDirective(node) should be true) function isUseStrictPrologueDirective(sourceFile: SourceFile, node: Node): boolean { Debug.assert(isPrologueDirective(node)); var nodeText = getSourceTextOfNodeFromSourceFile(sourceFile,(node).expression); @@ -451,7 +451,7 @@ module ts { } // Allows finding nodes in the source file at a certain position in an efficient manner. - // The implementation takes advantage of the calling pattern it knows hte parser will + // The implementation takes advantage of the calling pattern it knows the parser will // make in order to optimize finding nodes as quickly as possible. interface SyntaxCursor { currentNode(position: number): IncrementalNode; @@ -467,11 +467,14 @@ module ts { return { currentNode(position: number) { - // If the position is different than the last time we were asked. + // Only compute the current node if the position is different than the last time + // we were asked. The parser commonly asks for the node at the same position + // twice. Once to know if can read an appropriate list element at a certain point, + // and then to actually read and consume the node. if (position !== lastQueriedPosition) { - // Much of the time the parser will be need the very next node in the array - // that we just returned a node from. So just simply check for that case and - // move forward in the array instead of searching for the node again. + // Much of the time the parser will need the very next node in the array that + // we just returned a node from.So just simply check for that case and move + // forward in the array instead of searching for the node again. if (current && current.end === position && currentArrayIndex < currentArray.length) { currentArrayIndex++; current = currentArray[currentArrayIndex]; @@ -701,7 +704,7 @@ module ts { } if (sourceFile.statements.length === 0) { - // If we don't have any statements in the current source file, hten there's no real + // If we don't have any statements in the current source file, then there's no real // way to incrementally parse. So just do a full parse instead. return parseSourceFile(newText, /*setNodeParents*/ true); } @@ -765,23 +768,23 @@ module ts { // Node is entirely past the change range. We need to move both its pos and // end, forward or backward appropriately. moveElementEntirelyPastChangeRange(child, delta); + return; } - else { - // Check if the element intersects the change range. If it does, then it is not - // reusable. Also, we'll need to recurse to see what constituent portions we may - // be able to use. - var fullEnd = child.end; - if (fullEnd >= changeStart) { - child.intersectsChange = true; - // Adjust the pos or end (or both) of the intersecting element accordingly. - adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); - forEachChild(child, visitNode, visitArray); - } - // else { - // Otherwise, the node is entirely before the change range. No need to do anything with it. - // } + // Check if the element intersects the change range. If it does, then it is not + // reusable. Also, we'll need to recurse to see what constituent portions we may + // be able to use. + var fullEnd = child.end; + if (fullEnd >= changeStart) { + child.intersectsChange = true; + + // Adjust the pos or end (or both) of the intersecting element accordingly. + adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); + forEachChild(child, visitNode, visitArray); + return; } + + // Otherwise, the node is entirely before the change range. No need to do anything with it. } function visitArray(array: IncrementalNodeArray) { From b73128c50fae09473f1314bdf998df787d80cd1d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 16:17:48 -0800 Subject: [PATCH 39/50] CR feedback. --- src/services/outliningElementsCollector.ts | 10 ++++------ src/services/services.ts | 8 ++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index f4824192508..0b39f54ca29 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -79,12 +79,10 @@ module ts { break; } else if (tryStatement.finallyBlock === n) { - var children = tryStatement.getChildren(); - for (var i = 0, m = children.length; i < m; i++) { - if (children[i].kind === SyntaxKind.FinallyKeyword) { - addOutliningSpan(children[i], openBrace, closeBrace, autoCollapse(n)); - break; - } + var finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile); + if (finallyKeyword) { + addOutliningSpan(finallyKeyword, openBrace, closeBrace, autoCollapse(n)); + break; } } diff --git a/src/services/services.ts b/src/services/services.ts index ed17b59cf03..70921f73aef 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3510,12 +3510,8 @@ module ts { } if (tryStatement.finallyBlock) { - var children = tryStatement.getChildren(); - for (var i = 0, n = children.length; i < n; i++) { - if (pushKeywordIf(keywords, children[i], SyntaxKind.FinallyKeyword)) { - break; - } - } + var finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile); + pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword); } return map(keywords, getReferenceEntryFromNode); From f3ce5d763cd80cf271118bc3bdefc3ce82d0cc12 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 16:27:14 -0800 Subject: [PATCH 40/50] CR feedback. --- src/compiler/parser.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 45b33fc1bfb..0caaf414e94 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -457,13 +457,17 @@ module ts { currentNode(position: number): IncrementalNode; } + const enum InvalidPosition { + Value = -1 + } + function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { var currentArray: NodeArray = sourceFile.statements; var currentArrayIndex = 0; Debug.assert(currentArrayIndex < currentArray.length); var current = currentArray[currentArrayIndex]; - var lastQueriedPosition = -1; + var lastQueriedPosition = InvalidPosition.Value; return { currentNode(position: number) { From 7fc343eb43397dc7fb7d451bf50ef4182cecc44d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 16:27:26 -0800 Subject: [PATCH 41/50] Fix broken enum value alignment. --- src/compiler/types.ts | 136 ++++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7b800548eea..0843b413293 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1043,25 +1043,29 @@ module ts { } export const enum TypeFormatFlags { - None = 0x00000000, - WriteArrayAsGenericType = 0x00000001, // Write Array instead T[] - UseTypeOfFunction = 0x00000002, // Write typeof instead of function type literal - NoTruncation = 0x00000004, // Don't truncate typeToString result - WriteArrowStyleSignature = 0x00000008, // Write arrow style signature - WriteOwnNameForAnyLike = 0x00000010, // Write symbol's own name instead of 'any' for any like types (eg. unknown, __resolving__ etc) - WriteTypeArgumentsOfSignature = 0x00000020, // Write the type arguments instead of type parameters of the signature - InElementType = 0x00000040, // Writing an array or union element type + None = 0x00000000, + WriteArrayAsGenericType = 0x00000001, // Write Array instead T[] + UseTypeOfFunction = 0x00000002, // Write typeof instead of function type literal + NoTruncation = 0x00000004, // Don't truncate typeToString result + WriteArrowStyleSignature = 0x00000008, // Write arrow style signature + WriteOwnNameForAnyLike = 0x00000010, // Write symbol's own name instead of 'any' for any like types (eg. unknown, __resolving__ etc) + WriteTypeArgumentsOfSignature = 0x00000020, // Write the type arguments instead of type parameters of the signature + InElementType = 0x00000040, // Writing an array or union element type } export const enum SymbolFormatFlags { None = 0x00000000, - WriteTypeParametersOrArguments = 0x00000001, // Write symbols's type argument if it is instantiated symbol + + // Write symbols's type argument if it is instantiated symbol // eg. class C { p: T } <-- Show p as C.p here // var a: C; // var p = a.p; <--- Here p is property of C so show it as C.p instead of just C.p - UseOnlyExternalAliasing = 0x00000002, // Use only external alias information to get the symbol name in the given context + WriteTypeParametersOrArguments = 0x00000001, + + // Use only external alias information to get the symbol name in the given context // eg. module m { export class c { } } import x = m.c; // When this flag is specified m.c will be used to refer to the class instead of alias symbol x + UseOnlyExternalAliasing = 0x00000002, } export const enum SymbolAccessibility { @@ -1104,38 +1108,38 @@ module ts { } export const enum SymbolFlags { - FunctionScopedVariable = 0x00000001, // Variable (var) or parameter - BlockScopedVariable = 0x00000002, // A block-scoped variable (let or const) - Property = 0x00000004, // Property or enum member - EnumMember = 0x00000008, // Enum member - Function = 0x00000010, // Function - Class = 0x00000020, // Class - Interface = 0x00000040, // Interface - ConstEnum = 0x00000080, // Const enum - RegularEnum = 0x00000100, // Enum - ValueModule = 0x00000200, // Instantiated module - NamespaceModule = 0x00000400, // Uninstantiated module - TypeLiteral = 0x00000800, // Type Literal - ObjectLiteral = 0x00001000, // Object Literal - Method = 0x00002000, // Method - Constructor = 0x00004000, // Constructor - GetAccessor = 0x00008000, // Get accessor - SetAccessor = 0x00010000, // Set accessor - Signature = 0x00020000, // Call, construct, or index signature - TypeParameter = 0x00040000, // Type parameter - TypeAlias = 0x00080000, // Type alias + FunctionScopedVariable = 0x00000001, // Variable (var) or parameter + BlockScopedVariable = 0x00000002, // A block-scoped variable (let or const) + Property = 0x00000004, // Property or enum member + EnumMember = 0x00000008, // Enum member + Function = 0x00000010, // Function + Class = 0x00000020, // Class + Interface = 0x00000040, // Interface + ConstEnum = 0x00000080, // Const enum + RegularEnum = 0x00000100, // Enum + ValueModule = 0x00000200, // Instantiated module + NamespaceModule = 0x00000400, // Uninstantiated module + TypeLiteral = 0x00000800, // Type Literal + ObjectLiteral = 0x00001000, // Object Literal + Method = 0x00002000, // Method + Constructor = 0x00004000, // Constructor + GetAccessor = 0x00008000, // Get accessor + SetAccessor = 0x00010000, // Set accessor + Signature = 0x00020000, // Call, construct, or index signature + TypeParameter = 0x00040000, // Type parameter + TypeAlias = 0x00080000, // Type alias // Export markers (see comment in declareModuleMember in binder) - ExportValue = 0x00100000, // Exported value marker - ExportType = 0x00200000, // Exported type marker - ExportNamespace = 0x00400000, // Exported namespace marker - Import = 0x00800000, // Import - Instantiated = 0x01000000, // Instantiated symbol - Merged = 0x02000000, // Merged symbol (created during program binding) - Transient = 0x04000000, // Transient symbol (created during type check) - Prototype = 0x08000000, // Prototype property (no source representation) - UnionProperty = 0x10000000, // Property in union type - Optional = 0x20000000, // Optional property + ExportValue = 0x00100000, // Exported value marker + ExportType = 0x00200000, // Exported type marker + ExportNamespace = 0x00400000, // Exported namespace marker + Import = 0x00800000, // Import + Instantiated = 0x01000000, // Instantiated symbol + Merged = 0x02000000, // Merged symbol (created during program binding) + Transient = 0x04000000, // Transient symbol (created during type check) + Prototype = 0x08000000, // Prototype property (no source representation) + UnionProperty = 0x10000000, // Property in union type + Optional = 0x20000000, // Optional property Enum = RegularEnum | ConstEnum, Variable = FunctionScopedVariable | BlockScopedVariable, @@ -1214,16 +1218,16 @@ module ts { } export const enum NodeCheckFlags { - TypeChecked = 0x00000001, // Node has been type checked - LexicalThis = 0x00000002, // Lexical 'this' reference - CaptureThis = 0x00000004, // Lexical 'this' used in body - EmitExtends = 0x00000008, // Emit __extends - SuperInstance = 0x00000010, // Instance 'super' reference - SuperStatic = 0x00000020, // Static 'super' reference - ContextChecked = 0x00000040, // Contextual types have been assigned + TypeChecked = 0x00000001, // Node has been type checked + LexicalThis = 0x00000002, // Lexical 'this' reference + CaptureThis = 0x00000004, // Lexical 'this' used in body + EmitExtends = 0x00000008, // Emit __extends + SuperInstance = 0x00000010, // Instance 'super' reference + SuperStatic = 0x00000020, // Static 'super' reference + ContextChecked = 0x00000040, // Contextual types have been assigned // Values for enum members have been computed, and any errors have been reported for them. - EnumValuesComputed = 0x00000080, + EnumValuesComputed = 0x00000080, } export interface NodeLinks { @@ -1240,24 +1244,24 @@ module ts { } export const enum TypeFlags { - Any = 0x00000001, - String = 0x00000002, - Number = 0x00000004, - Boolean = 0x00000008, - Void = 0x00000010, - Undefined = 0x00000020, - Null = 0x00000040, - Enum = 0x00000080, // Enum type - StringLiteral = 0x00000100, // String literal type - TypeParameter = 0x00000200, // Type parameter - Class = 0x00000400, // Class - Interface = 0x00000800, // Interface - Reference = 0x00001000, // Generic type reference - Tuple = 0x00002000, // Tuple - Union = 0x00004000, // Union - Anonymous = 0x00008000, // Anonymous - FromSignature = 0x00010000, // Created for signature assignment check - Unwidened = 0x00020000, // Unwidened type (is or contains Undefined or Null type) + Any = 0x00000001, + String = 0x00000002, + Number = 0x00000004, + Boolean = 0x00000008, + Void = 0x00000010, + Undefined = 0x00000020, + Null = 0x00000040, + Enum = 0x00000080, // Enum type + StringLiteral = 0x00000100, // String literal type + TypeParameter = 0x00000200, // Type parameter + Class = 0x00000400, // Class + Interface = 0x00000800, // Interface + Reference = 0x00001000, // Generic type reference + Tuple = 0x00002000, // Tuple + Union = 0x00004000, // Union + Anonymous = 0x00008000, // Anonymous + FromSignature = 0x00010000, // Created for signature assignment check + Unwidened = 0x00020000, // Unwidened type (is or contains Undefined or Null type) Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null, StringLike = String | StringLiteral, From 535f9d8972c1ac0384ae22984928bc4294de545d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 16:36:49 -0800 Subject: [PATCH 42/50] Rename method to be clearer, and add comments to explain the semantics. --- src/compiler/parser.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0caaf414e94..56366c9d2f4 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -487,7 +487,7 @@ module ts { // If we don't have a node, or the node we have isn't in the right position, // then try to find a viable node at the position requested. if (!current || current.pos !== position) { - findHighestNodeAtPosition(position); + findHighestListElementThatStartsAtPosition(position); } } @@ -503,8 +503,11 @@ module ts { return current; } }; - - function findHighestNodeAtPosition(position: number) { + + // Finds the highest element in the tree we can find that starts at the provided position. + // The element must be a direct child of some node list in the tree. This way after we + // return it, we can easily return its next sibling in the list. + function findHighestListElementThatStartsAtPosition(position: number) { // Clear out any cached state about the last node we found. currentArray = undefined; currentArrayIndex = -1; From fab4955ef7d480701f7836cf3c2a7dd9e6b37360 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 17:10:21 -0800 Subject: [PATCH 43/50] Add explanatory comments. --- src/compiler/checker.ts | 3 +++ src/compiler/utilities.ts | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4c9af07873..8312420b355 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1619,6 +1619,9 @@ module ts { function getDeclarationContainer(node: Node): Node { node = getRootDeclaration(node); + + // Parent chain: + // VaribleDeclaration -> VariableDeclarationList -> VariableStatement -> 'Declaration Container' return node.kind === SyntaxKind.VariableDeclaration ? node.parent.parent.parent : node.parent; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2db5d9e3b7d..f8312a03d10 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -110,6 +110,18 @@ module ts { 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: + // + // var 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; @@ -117,7 +129,7 @@ module ts { return node.pos === node.end && node.kind !== SyntaxKind.EndOfFileToken; } - + export function nodeIsPresent(node: Node) { return !nodeIsMissing(node); } @@ -780,7 +792,7 @@ module ts { throw new Error("start < 0"); } if (length < 0) { - throw new Error("start < 0"); + throw new Error("length < 0"); } this._start = start; this._length = length; From dfb1ac0f00fe009c4db9829d56fe542935bc4ff3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 17:14:45 -0800 Subject: [PATCH 44/50] Use constant in another place. --- src/compiler/parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 56366c9d2f4..6173ac89fbb 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -510,7 +510,7 @@ module ts { function findHighestListElementThatStartsAtPosition(position: number) { // Clear out any cached state about the last node we found. currentArray = undefined; - currentArrayIndex = -1; + currentArrayIndex = InvalidPosition.Value; current = undefined; // Recurse into the source file to find the highest node at this position. From 7f3a73b7c862441f382e07f82cb6dbc36c54ce62 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 18:31:06 -0800 Subject: [PATCH 45/50] Change 'TextSpan' to be a simple record type with free floating functions. --- src/compiler/parser.ts | 10 +- src/compiler/types.ts | 14 +-- src/compiler/utilities.ts | 130 +++++++++++--------------- src/harness/fourslash.ts | 58 ++++++------ src/harness/harnessLanguageService.ts | 12 +-- src/services/services.ts | 18 ++-- 6 files changed, 108 insertions(+), 134 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6173ac89fbb..46959cf435f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -725,7 +725,7 @@ module ts { // The is the amount the nodes after the edit range need to be adjusted. It can be // positive (if the edit added characters), negative (if the edit deleted characters) // or zero (if this was a pure overwrite with nothing added/removed). - var delta = changeRange.newSpan().length() - changeRange.span().length(); + var delta = changeRange.newSpan().length - changeRange.span().length; // If we added or removed characters during the edit, then we need to go and adjust all // the nodes after the edit. Those nodes may move forward down (if we inserted chars) @@ -747,7 +747,7 @@ module ts { // Also, mark any syntax elements that intersect the changed span. We know, up front, // that we cannot reuse these elements. updateTokenPositionsAndMarkElements(sourceFile, - changeRange.span().start(), changeRange.span().end(), changeRange.newSpan().end(), delta); + changeRange.span().start, textSpanEnd(changeRange.span()), textSpanEnd(changeRange.newSpan()), delta); // Now that we've set up our internal incremental state just proceed and parse the // source file in the normal fashion. When possible the parser will retrieve and @@ -936,7 +936,7 @@ module ts { // that the prior token sees that change. var maxLookahead = 1; - var start = changeRange.span().start(); + var start = changeRange.span().start; // the first iteration aligns us with the change start. subsequent iteration move us to // the left by maxLookahead tokens. We only need to do this as long as we're not at the @@ -948,8 +948,8 @@ module ts { start = Math.max(0, position - 1); } - var finalSpan = createTextSpanFromBounds(start, changeRange.span().end()); - var finalLength = changeRange.newLength() + (changeRange.span().start() - start); + var finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span())); + var finalLength = changeRange.newLength() + (changeRange.span().start - start); return createTextChangeRange(finalSpan, finalLength); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0843b413293..0707cf9975b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1637,18 +1637,8 @@ module ts { } export interface TextSpan { - start(): number; - length(): number; - end(): number; - isEmpty(): boolean; - containsPosition(position: number): boolean; - containsTextSpan(span: TextSpan): boolean; - overlapsWith(span: TextSpan): boolean; - overlap(span: TextSpan): TextSpan; - intersectsWithTextSpan(span: TextSpan): boolean; - intersectsWith(start: number, length: number): boolean; - intersectsWithPosition(position: number): boolean; - intersection(span: TextSpan): TextSpan; + start: number; + length: number; } export interface TextChangeRange { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f8312a03d10..5d795b1d1ae 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -786,78 +786,62 @@ module ts { return false; } - var textSpanConstructor = (function () { - function textSpanConstructor(start: number, length: number) { - if (start < 0) { - throw new Error("start < 0"); - } - if (length < 0) { - throw new Error("length < 0"); - } - this._start = start; - this._length = length; + 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) { + var overlapStart = Math.max(span.start, other.start); + var overlapEnd = Math.min(textSpanEnd(span), textSpanEnd(other)); + return overlapStart < overlapEnd; + } + + export function textSpanOverlap(span1: TextSpan, span2: TextSpan) { + var overlapStart = Math.max(span1.start, span2.start); + var overlapEnd = Math.min(textSpanEnd(span1), textSpanEnd(span2)); + if (overlapStart < overlapEnd) { + return createTextSpanFromBounds(overlapStart, overlapEnd); } + return undefined; + } - textSpanConstructor.prototype = { - toJSON(key: string) { - return { start: this._start, length: this._length } - }, - start() { - return this._start - }, - length() { - return this._length - }, - end() { - return this._start + this._length - }, - isEmpty() { - return this._length === 0 - }, - containsPosition(position: number) { - return position >= this._start && position < this.end() - }, - containsTextSpan(span: TextSpan) { - return span.start() >= this._start && span.end() <= this.end() - }, - overlapsWith(span: TextSpan) { - var overlapStart = Math.max(this._start, span.start()); - var overlapEnd = Math.min(this.end(), span.end()); - return overlapStart < overlapEnd; - }, - overlap(span: TextSpan) { - var overlapStart = Math.max(this._start, span.start()); - var overlapEnd = Math.min(this.end(), span.end()); - if (overlapStart < overlapEnd) { - return createTextSpanFromBounds(overlapStart, overlapEnd); - } - return undefined; - }, - intersectsWithTextSpan(span: TextSpan) { - return span.start() <= this.end() && span.end() >= this._start - }, - intersectsWith(start: number, length: number) { - var end = start + length; - return start <= this.end() && end >= this._start; - }, - intersectsWithPosition(position: number) { - return position <= this.end() && position >= this._start; - }, - intersection(span: TextSpan) { - var intersectStart = Math.max(this._start, span.start()); - var intersectEnd = Math.min(this.end(), span.end()); - if (intersectStart <= intersectEnd) { - return createTextSpanFromBounds(intersectStart, intersectEnd); - } - return undefined; - } - }; + export function textSpanIntersectsWithTextSpan(span: TextSpan, other: TextSpan) { + return other.start <= textSpanEnd(span) && textSpanEnd(other) >= span.start + } - return textSpanConstructor; - })(); + export function textSpanIntersectsWith(span: TextSpan, start: number, length: number) { + var 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) { + var intersectStart = Math.max(span1.start, span2.start); + var intersectEnd = Math.min(textSpanEnd(span1), textSpanEnd(span2)); + if (intersectStart <= intersectEnd) { + return createTextSpanFromBounds(intersectStart, intersectEnd); + } + return undefined; + } export function createTextSpan(start: number, length: number): TextSpan { - return new (textSpanConstructor)(start, length); + return { start, length }; } export function createTextSpanFromBounds(start: number, end: number) { @@ -881,10 +865,10 @@ module ts { return this._newLength; }, newSpan() { - return createTextSpan(this.span().start(), this.newLength()); + return createTextSpan(this.span().start, this.newLength()); }, isUnchanged() { - return this.span().isEmpty() && this.newLength() === 0; + return textSpanIsEmpty(this.span()) && this.newLength() === 0; } }; @@ -918,8 +902,8 @@ module ts { // as it makes things much easier to reason about. var change0 = changes[0]; - var oldStartN = change0.span().start(); - var oldEndN = change0.span().end(); + var oldStartN = change0.span().start; + var oldEndN = textSpanEnd(change0.span()); var newEndN = oldStartN + change0.newLength(); for (var i = 1; i < changes.length; i++) { @@ -1009,8 +993,8 @@ module ts { var oldEnd1 = oldEndN; var newEnd1 = newEndN; - var oldStart2 = nextChange.span().start(); - var oldEnd2 = nextChange.span().end(); + var oldStart2 = nextChange.span().start; + var oldEnd2 = textSpanEnd(nextChange.span()); var newEnd2 = oldStart2 + nextChange.newLength(); oldStartN = Math.min(oldStart1, oldStart2); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 452e52d3ded..f8086d0ed52 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -697,7 +697,7 @@ module FourSlash { for (var i = 0; i < references.length; i++) { var reference = references[i]; - if (reference && reference.fileName === fileName && reference.textSpan.start() === start && reference.textSpan.end() === end) { + if (reference && reference.fileName === fileName && reference.textSpan.start === start && ts.textSpanEnd(reference.textSpan) === end) { if (typeof isWriteAccess !== "undefined" && reference.isWriteAccess !== isWriteAccess) { this.raiseError('verifyReferencesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + reference.isWriteAccess + ', expected: ' + isWriteAccess + '.'); } @@ -828,17 +828,17 @@ module FourSlash { } ranges = ranges.sort((r1, r2) => r1.start - r2.start); - references = references.sort((r1, r2) => r1.textSpan.start() - r2.textSpan.start()); + references = references.sort((r1, r2) => r1.textSpan.start - r2.textSpan.start); for (var i = 0, n = ranges.length; i < n; i++) { var reference = references[i]; var range = ranges[i]; - if (reference.textSpan.start() !== range.start || - reference.textSpan.end() !== range.end) { + if (reference.textSpan.start !== range.start || + ts.textSpanEnd(reference.textSpan) !== range.end) { this.raiseError(this.assertionMessage("Rename location", - "[" + reference.textSpan.start() + "," + reference.textSpan.end() + ")", + "[" + reference.textSpan.start + "," + ts.textSpanEnd(reference.textSpan) + ")", "[" + range.start + "," + range.end + ")")); } } @@ -978,10 +978,10 @@ module FourSlash { } var expectedRange = this.getRanges()[0]; - if (renameInfo.triggerSpan.start() !== expectedRange.start || - renameInfo.triggerSpan.end() !== expectedRange.end) { + if (renameInfo.triggerSpan.start !== expectedRange.start || + ts.textSpanEnd(renameInfo.triggerSpan) !== expectedRange.end) { this.raiseError("Expected triggerSpan [" + expectedRange.start + "," + expectedRange.end + "). Got [" + - renameInfo.triggerSpan.start() + "," + renameInfo.triggerSpan.end() + ") instead."); + renameInfo.triggerSpan.start + "," + ts.textSpanEnd(renameInfo.triggerSpan) + ") instead."); } } @@ -1012,7 +1012,7 @@ module FourSlash { private spanInfoToString(pos: number, spanInfo: ts.TextSpan, prefixString: string) { var resultString = "SpanInfo: " + JSON.stringify(spanInfo); if (spanInfo) { - var spanString = this.activeFile.content.substr(spanInfo.start(), spanInfo.length()); + var spanString = this.activeFile.content.substr(spanInfo.start, spanInfo.length); var spanLineMap = ts.computeLineStarts(spanString); for (var i = 0; i < spanLineMap.length; i++) { if (!i) { @@ -1020,7 +1020,7 @@ module FourSlash { } resultString += prefixString + spanString.substring(spanLineMap[i], spanLineMap[i + 1]); } - resultString += "\n" + prefixString + ":=> (" + this.getLineColStringAtPosition(spanInfo.start()) + ") to (" + this.getLineColStringAtPosition(spanInfo.end()) + ")"; + resultString += "\n" + prefixString + ":=> (" + this.getLineColStringAtPosition(spanInfo.start) + ") to (" + this.getLineColStringAtPosition(ts.textSpanEnd(spanInfo)) + ")"; } return resultString; @@ -1411,14 +1411,14 @@ module FourSlash { // We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track // of the incremental offset from each edit to the next. Assumption is that these edit ranges don't overlap var runningOffset = 0; - edits = edits.sort((a, b) => a.span.start() - b.span.start()); + edits = edits.sort((a, b) => a.span.start - b.span.start); // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters var snapshot = this.languageServiceShimHost.getScriptSnapshot(fileName); var oldContent = snapshot.getText(0, snapshot.getLength()); for (var j = 0; j < edits.length; j++) { - this.languageServiceShimHost.editScript(fileName, edits[j].span.start() + runningOffset, edits[j].span.end() + runningOffset, edits[j].newText); - this.updateMarkersForEdit(fileName, edits[j].span.start() + runningOffset, edits[j].span.end() + runningOffset, edits[j].newText); - var change = (edits[j].span.start() - edits[j].span.end()) + edits[j].newText.length; + this.languageServiceShimHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText); + this.updateMarkersForEdit(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText); + var change = (edits[j].span.start - ts.textSpanEnd(edits[j].span)) + edits[j].newText.length; runningOffset += change; // TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150) // this.languageService.getScriptLexicalStructure(fileName); @@ -1495,7 +1495,7 @@ module FourSlash { var definition = definitions[definitionIndex]; this.openFile(definition.fileName); - this.currentCaretPosition = definition.textSpan.start(); + this.currentCaretPosition = definition.textSpan.start; } public verifyDefinitionLocationExists(negative: boolean) { @@ -1616,7 +1616,7 @@ module FourSlash { '\t Actual: undefined'); } - var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.start(), span.end()); + var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.start, ts.textSpanEnd(span)); if (actual !== text) { this.raiseError('verifyCurrentNameOrDottedNameSpanText\n' + '\tExpected: "' + text + '"\n' + @@ -1671,15 +1671,15 @@ module FourSlash { if (expectedSpan) { var expectedLength = expectedSpan.end - expectedSpan.start; - if (expectedSpan.start !== actualSpan.start() || expectedLength !== actualSpan.length()) { + if (expectedSpan.start !== actualSpan.start || expectedLength !== actualSpan.length) { this.raiseError("verifyClassifications failed - expected span of text to be " + "{start=" + expectedSpan.start + ", length=" + expectedLength + "}, but was " + - "{start=" + actualSpan.start() + ", length=" + actualSpan.length() + "}" + + "{start=" + actualSpan.start + ", length=" + actualSpan.length + "}" + jsonMismatchString()); } } - var actualText = this.activeFile.content.substr(actualSpan.start(), actualSpan.length()); + var actualText = this.activeFile.content.substr(actualSpan.start, actualSpan.length); if (expectedClassification.text !== actualText) { this.raiseError('verifyClassifications failed - expected classified text to be ' + expectedClassification.text + ', but was ' + @@ -1721,8 +1721,8 @@ module FourSlash { for (var i = 0; i < spans.length; i++) { var expectedSpan = spans[i]; var actualSpan = actual[i]; - if (expectedSpan.start !== actualSpan.textSpan.start() || expectedSpan.end !== actualSpan.textSpan.end()) { - this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualSpan.textSpan.start() + ',' + actualSpan.textSpan.end() + ')'); + if (expectedSpan.start !== actualSpan.textSpan.start || expectedSpan.end !== ts.textSpanEnd(actualSpan.textSpan)) { + this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualSpan.textSpan.start + ',' + ts.textSpanEnd(actualSpan.textSpan) + ')'); } } } @@ -1740,8 +1740,8 @@ module FourSlash { var actualComment = actual[i]; var actualCommentSpan = ts.createTextSpan(actualComment.position, actualComment.message.length); - if (expectedSpan.start !== actualCommentSpan.start() || expectedSpan.end !== actualCommentSpan.end()) { - this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start() + ',' + actualCommentSpan.end() + ')'); + if (expectedSpan.start !== actualCommentSpan.start || expectedSpan.end !== ts.textSpanEnd(actualCommentSpan)) { + this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start + ',' + ts.textSpanEnd(actualCommentSpan) + ')'); } } } @@ -1756,12 +1756,12 @@ module FourSlash { } var actualMatchPosition = -1; - if (bracePosition === actual[0].start()) { - actualMatchPosition = actual[1].start(); - } else if (bracePosition === actual[1].start()) { - actualMatchPosition = actual[0].start(); + if (bracePosition === actual[0].start) { + actualMatchPosition = actual[1].start; + } else if (bracePosition === actual[1].start) { + actualMatchPosition = actual[0].start; } else { - this.raiseError('verifyMatchingBracePosition failed - could not find the brace position: ' + bracePosition + ' in the returned list: (' + actual[0].start() + ',' + actual[0].end() + ') and (' + actual[1].start() + ',' + actual[1].end() + ')'); + this.raiseError('verifyMatchingBracePosition failed - could not find the brace position: ' + bracePosition + ' in the returned list: (' + actual[0].start + ',' + ts.textSpanEnd(actual[0]) + ') and (' + actual[1].start + ',' + ts.textSpanEnd(actual[1]) + ')'); } if (actualMatchPosition !== expectedMatchPosition) { @@ -2001,7 +2001,7 @@ module FourSlash { for (var i = 0; i < occurances.length; i++) { var occurance = occurances[i]; - if (occurance && occurance.fileName === fileName && occurance.textSpan.start() === start && occurance.textSpan.end() === end) { + if (occurance && occurance.fileName === fileName && occurance.textSpan.start === start && ts.textSpanEnd(occurance.textSpan) === end) { if (typeof isWriteAccess !== "undefined" && occurance.isWriteAccess !== isWriteAccess) { this.raiseError('verifyOccurancesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + occurance.isWriteAccess + ', expected: ' + isWriteAccess + '.'); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 430dcc24477..23555411989 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -87,7 +87,7 @@ module Harness.LanguageService { return null; } - return JSON.stringify({ span: { start: range.span().start(), length: range.span().length() }, newLength: range.newLength() }); + return JSON.stringify({ span: { start: range.span().start, length: range.span().length }, newLength: range.newLength() }); } } @@ -346,9 +346,9 @@ module Harness.LanguageService { for (var i = edits.length - 1; i >= 0; i--) { var edit = edits[i]; - var prefix = result.substring(0, edit.span.start()); + var prefix = result.substring(0, edit.span.start); var middle = edit.newText; - var suffix = result.substring(edit.span.end()); + var suffix = result.substring(ts.textSpanEnd(edit.span)); result = prefix + middle + suffix; } return result; @@ -367,7 +367,7 @@ module Harness.LanguageService { } var temp = mapEdits(edits).sort(function (a, b) { - var result = a.edit.span.start() - b.edit.span.start(); + var result = a.edit.span.start - b.edit.span.start; if (result === 0) result = a.index - b.index; return result; @@ -386,7 +386,7 @@ module Harness.LanguageService { } var nextEdit = temp[next].edit; - var gap = nextEdit.span.start() - currentEdit.span.end(); + var gap = nextEdit.span.start - ts.textSpanEnd(currentEdit.span); // non-overlapping edits if (gap >= 0) { @@ -398,7 +398,7 @@ module Harness.LanguageService { // overlapping edits: for now, we only support ignoring an next edit // entirely contained in the current edit. - if (currentEdit.span.end() >= nextEdit.span.end()) { + if (ts.textSpanEnd(currentEdit.span) >= ts.textSpanEnd(nextEdit.span)) { next++; continue; } diff --git a/src/services/services.ts b/src/services/services.ts index 70921f73aef..6fb64baf03f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1561,15 +1561,15 @@ module ts { var oldText = sourceFile.scriptSnapshot; var newText = scriptSnapshot; - Debug.assert((oldText.getLength() - textChangeRange.span().length() + textChangeRange.newLength()) === newText.getLength()); + Debug.assert((oldText.getLength() - textChangeRange.span().length + textChangeRange.newLength()) === newText.getLength()); if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - var oldTextPrefix = oldText.getText(0, textChangeRange.span().start()); - var newTextPrefix = newText.getText(0, textChangeRange.span().start()); + var oldTextPrefix = oldText.getText(0, textChangeRange.span().start); + var newTextPrefix = newText.getText(0, textChangeRange.span().start); Debug.assert(oldTextPrefix === newTextPrefix); - var oldTextSuffix = oldText.getText(textChangeRange.span().end(), oldText.getLength()); - var newTextSuffix = newText.getText(textChangeRange.newSpan().end(), newText.getLength()); + var oldTextSuffix = oldText.getText(textSpanEnd(textChangeRange.span()), oldText.getLength()); + var newTextSuffix = newText.getText(textSpanEnd(textChangeRange.newSpan()), newText.getLength()); Debug.assert(oldTextSuffix === newTextSuffix); } } @@ -4852,7 +4852,7 @@ module ts { function processNode(node: Node) { // Only walk into nodes that intersect the requested span. - if (node && span.intersectsWith(node.getStart(), node.getWidth())) { + if (node && textSpanIntersectsWith(span, node.getStart(), node.getWidth())) { if (node.kind === SyntaxKind.Identifier && node.getWidth() > 0) { var symbol = typeInfoResolver.getSymbolAtLocation(node); if (symbol) { @@ -4883,7 +4883,7 @@ module ts { function classifyComment(comment: CommentRange) { var width = comment.end - comment.pos; - if (span.intersectsWith(comment.pos, width)) { + if (textSpanIntersectsWith(span, comment.pos, width)) { result.push({ textSpan: createTextSpan(comment.pos, width), classificationType: ClassificationTypeNames.comment @@ -4985,7 +4985,7 @@ module ts { function processElement(element: Node) { // Ignore nodes that don't intersect the original span to classify. - if (span.intersectsWith(element.getFullStart(), element.getFullWidth())) { + if (textSpanIntersectsWith(span, element.getFullStart(), element.getFullWidth())) { var children = element.getChildren(); for (var i = 0, n = children.length; i < n; i++) { var child = children[i]; @@ -5030,7 +5030,7 @@ module ts { var range2 = createTextSpan(current.getStart(sourceFile), current.getWidth(sourceFile)); // We want to order the braces when we return the result. - if (range1.start() < range2.start()) { + if (range1.start < range2.start) { result.push(range1, range2); } else { From 9df59c39eede12ff4aeb6b854a802b5e812117f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 18:40:15 -0800 Subject: [PATCH 46/50] Change TextChangeRange to be a record type as well. --- src/compiler/parser.ts | 12 +++--- src/compiler/types.ts | 6 +-- src/compiler/utilities.ts | 58 ++++++++++++--------------- src/harness/harnessLanguageService.ts | 2 +- src/services/services.ts | 10 ++--- 5 files changed, 39 insertions(+), 49 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 46959cf435f..784d0de907f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -705,7 +705,7 @@ module ts { } function update(newText: string, textChangeRange: TextChangeRange) { - if (textChangeRange.isUnchanged()) { + if (textChangeRangeIsUnchanged(textChangeRange)) { // if the text didn't change, then we can just return our current source file as-is. return sourceFile; } @@ -725,7 +725,7 @@ module ts { // The is the amount the nodes after the edit range need to be adjusted. It can be // positive (if the edit added characters), negative (if the edit deleted characters) // or zero (if this was a pure overwrite with nothing added/removed). - var delta = changeRange.newSpan().length - changeRange.span().length; + var delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length; // If we added or removed characters during the edit, then we need to go and adjust all // the nodes after the edit. Those nodes may move forward down (if we inserted chars) @@ -747,7 +747,7 @@ module ts { // Also, mark any syntax elements that intersect the changed span. We know, up front, // that we cannot reuse these elements. updateTokenPositionsAndMarkElements(sourceFile, - changeRange.span().start, textSpanEnd(changeRange.span()), textSpanEnd(changeRange.newSpan()), delta); + changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta); // Now that we've set up our internal incremental state just proceed and parse the // source file in the normal fashion. When possible the parser will retrieve and @@ -936,7 +936,7 @@ module ts { // that the prior token sees that change. var maxLookahead = 1; - var start = changeRange.span().start; + var start = changeRange.span.start; // the first iteration aligns us with the change start. subsequent iteration move us to // the left by maxLookahead tokens. We only need to do this as long as we're not at the @@ -948,8 +948,8 @@ module ts { start = Math.max(0, position - 1); } - var finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span())); - var finalLength = changeRange.newLength() + (changeRange.span().start - start); + var finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); + var finalLength = changeRange.newLength + (changeRange.span.start - start); return createTextChangeRange(finalSpan, finalLength); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0707cf9975b..815d2b23e51 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1642,9 +1642,7 @@ module ts { } export interface TextChangeRange { - span(): TextSpan; - newLength(): number; - newSpan(): TextSpan; - isUnchanged(): boolean; + span: TextSpan; + newLength: number; } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5d795b1d1ae..4c5f75a319c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -841,42 +841,34 @@ module ts { } 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); + } - var textChangeRangeConstructor = (function () { - function textChangeRangeConstructor(span: TextSpan, newLength: number) { - if (newLength < 0) { - throw new Error("newLength < 0"); - } - this._span = span; - this._newLength = newLength; - } - - textChangeRangeConstructor.prototype = { - span() { - return this._span; - }, - newLength() { - return this._newLength; - }, - newSpan() { - return createTextSpan(this.span().start, this.newLength()); - }, - isUnchanged() { - return textSpanIsEmpty(this.span()) && this.newLength() === 0; - } - }; - - return textChangeRangeConstructor; - })(); + export function textChangeRangeIsUnchanged(range: TextChangeRange) { + return textSpanIsEmpty(range.span) && range.newLength === 0; + } export function createTextChangeRange(span: TextSpan, newLength: number): TextChangeRange { - return new (textChangeRangeConstructor)(span, newLength); + if (newLength < 0) { + throw new Error("newLength < 0"); + } + + return { span, newLength }; } export var unchangedTextChangeRange = createTextChangeRange(createTextSpan(0, 0), 0); @@ -902,9 +894,9 @@ module ts { // as it makes things much easier to reason about. var change0 = changes[0]; - var oldStartN = change0.span().start; - var oldEndN = textSpanEnd(change0.span()); - var newEndN = oldStartN + change0.newLength(); + var oldStartN = change0.span.start; + var oldEndN = textSpanEnd(change0.span); + var newEndN = oldStartN + change0.newLength; for (var i = 1; i < changes.length; i++) { var nextChange = changes[i]; @@ -993,9 +985,9 @@ module ts { var oldEnd1 = oldEndN; var newEnd1 = newEndN; - var oldStart2 = nextChange.span().start; - var oldEnd2 = textSpanEnd(nextChange.span()); - var newEnd2 = oldStart2 + nextChange.newLength(); + var oldStart2 = nextChange.span.start; + var oldEnd2 = textSpanEnd(nextChange.span); + var newEnd2 = oldStart2 + nextChange.newLength; oldStartN = Math.min(oldStart1, oldStart2); oldEndN = Math.max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 23555411989..97885bdd20e 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -87,7 +87,7 @@ module Harness.LanguageService { return null; } - return JSON.stringify({ span: { start: range.span().start, length: range.span().length }, newLength: range.newLength() }); + return JSON.stringify({ span: { start: range.span.start, length: range.span.length }, newLength: range.newLength }); } } diff --git a/src/services/services.ts b/src/services/services.ts index 6fb64baf03f..84811489a7c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1561,15 +1561,15 @@ module ts { var oldText = sourceFile.scriptSnapshot; var newText = scriptSnapshot; - Debug.assert((oldText.getLength() - textChangeRange.span().length + textChangeRange.newLength()) === newText.getLength()); + Debug.assert((oldText.getLength() - textChangeRange.span.length + textChangeRange.newLength) === newText.getLength()); if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - var oldTextPrefix = oldText.getText(0, textChangeRange.span().start); - var newTextPrefix = newText.getText(0, textChangeRange.span().start); + var oldTextPrefix = oldText.getText(0, textChangeRange.span.start); + var newTextPrefix = newText.getText(0, textChangeRange.span.start); Debug.assert(oldTextPrefix === newTextPrefix); - var oldTextSuffix = oldText.getText(textSpanEnd(textChangeRange.span()), oldText.getLength()); - var newTextSuffix = newText.getText(textSpanEnd(textChangeRange.newSpan()), newText.getLength()); + var oldTextSuffix = oldText.getText(textSpanEnd(textChangeRange.span), oldText.getLength()); + var newTextSuffix = newText.getText(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.getLength()); Debug.assert(oldTextSuffix === newTextSuffix); } } From 7f893f9b9ad50e0021488288ce1c3fec1775cf89 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 18:50:34 -0800 Subject: [PATCH 47/50] Rename method to be clearer. --- src/compiler/binder.ts | 4 ++-- src/compiler/checker.ts | 10 +++++----- src/compiler/emitter.ts | 4 ++-- src/compiler/utilities.ts | 13 ++++++++++--- src/services/utilities.ts | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f21ecafd254..6b2dafaa19d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -207,7 +207,7 @@ module ts { exportKind |= SymbolFlags.ExportNamespace; } - if (getNodeFlags(node) & NodeFlags.Export || (node.kind !== SyntaxKind.ImportDeclaration && isAmbientContext(container))) { + if (getCombinedNodeFlags(node) & NodeFlags.Export || (node.kind !== SyntaxKind.ImportDeclaration && isAmbientContext(container))) { if (exportKind) { var local = declareSymbol(container.locals, undefined, node, exportKind, symbolExcludes); local.exportSymbol = declareSymbol(container.symbol.exports, container.symbol, node, symbolKind, symbolExcludes); @@ -390,7 +390,7 @@ module ts { if (isBindingPattern((node).name)) { bindChildren(node, 0, /*isBlockScopeContainer*/ false); } - else if (getNodeFlags(node) & NodeFlags.BlockScoped) { + else if (getCombinedNodeFlags(node) & NodeFlags.BlockScoped) { bindBlockScopedVariableDeclaration(node); } else { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8312420b355..9c710d1b1af 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -406,7 +406,7 @@ module ts { } if (result.flags & SymbolFlags.BlockScopedVariable) { // Block-scoped variables cannot be used before their definition - var declaration = forEach(result.declarations, d => getNodeFlags(d) & NodeFlags.BlockScoped ? d : undefined); + var declaration = forEach(result.declarations, d => getCombinedNodeFlags(d) & NodeFlags.BlockScoped ? d : undefined); Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined"); if (!isDefinedBefore(declaration, errorLocation)) { error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name)); @@ -1556,7 +1556,7 @@ module ts { case SyntaxKind.ImportDeclaration: var parent = getDeclarationContainer(node); // If the node is not exported or it is not ambient module element (except import declaration) - if (!(getNodeFlags(node) & NodeFlags.Export) && + if (!(getCombinedNodeFlags(node) & NodeFlags.Export) && !(node.kind !== SyntaxKind.ImportDeclaration && parent.kind !== SyntaxKind.SourceFile && isInAmbientContext(parent))) { return isGlobalSourceFile(parent) || isUsedInExportAssignment(node); } @@ -5422,7 +5422,7 @@ module ts { } function getDeclarationFlagsFromSymbol(s: Symbol) { - return s.valueDeclaration ? getNodeFlags(s.valueDeclaration) : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0; + return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0; } function checkClassPropertyAccess(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, type: Type, prop: Symbol) { @@ -7308,7 +7308,7 @@ module ts { } function getEffectiveDeclarationFlags(n: Node, flagsToCheck: NodeFlags) { - var flags = getNodeFlags(n); + var flags = getCombinedNodeFlags(n); if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && isInAmbientContext(n)) { if (!(flags & NodeFlags.Ambient)) { // It is nested in an ambient context, which means it is automatically exported @@ -7784,7 +7784,7 @@ module ts { // const x = 0; // var x = 0; // } - if (node.initializer && (getNodeFlags(node) & NodeFlags.BlockScoped) === 0) { + if (node.initializer && (getCombinedNodeFlags(node) & NodeFlags.BlockScoped) === 0) { var symbol = getSymbolOfNode(node); if (symbol.flags & SymbolFlags.FunctionScopedVariable) { var localDeclarationSymbol = resolveName(node, (node.name).text, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 6db57460378..786e5a99259 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2772,7 +2772,7 @@ module ts { function emitModuleMemberName(node: Declaration) { emitStart(node.name); - if (getNodeFlags(node) & NodeFlags.Export) { + if (getCombinedNodeFlags(node) & NodeFlags.Export) { var container = getContainingModule(node); write(container ? resolver.getLocalNameOfContainer(container) : "exports"); write("."); @@ -2785,7 +2785,7 @@ module ts { var emitCount = 0; // An exported declaration is actually emitted as an assignment (to a property on the module object), so // temporary variables in an exported declaration need to have real declarations elsewhere - var isDeclaration = (root.kind === SyntaxKind.VariableDeclaration && !(getNodeFlags(root) & NodeFlags.Export)) || root.kind === SyntaxKind.Parameter; + var isDeclaration = (root.kind === SyntaxKind.VariableDeclaration && !(getCombinedNodeFlags(root) & NodeFlags.Export)) || root.kind === SyntaxKind.Parameter; if (root.kind === SyntaxKind.BinaryExpression) { emitAssignmentExpression(root); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4c5f75a319c..62487680759 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -244,7 +244,14 @@ module ts { return node; } - export function getNodeFlags(node: Node): NodeFlags { + // 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); var flags = node.flags; @@ -265,11 +272,11 @@ module ts { } export function isConst(node: Node): boolean { - return !!(getNodeFlags(node) & NodeFlags.Const); + return !!(getCombinedNodeFlags(node) & NodeFlags.Const); } export function isLet(node: Node): boolean { - return !!(getNodeFlags(node) & NodeFlags.Let); + return !!(getCombinedNodeFlags(node) & NodeFlags.Let); } export function isPrologueDirective(node: Node): boolean { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 1b7fa1bea64..10d00398160 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -271,7 +271,7 @@ module ts { } export function getNodeModifiers(node: Node): string { - var flags = getNodeFlags(node); + var flags = getCombinedNodeFlags(node); var result: string[] = []; if (flags & NodeFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); From 908d4f61e6f683fc9c14e73e28da27719f0498ae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 18:54:21 -0800 Subject: [PATCH 48/50] Provide a stronger type for the parent of a variable declaration. --- src/compiler/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 815d2b23e51..8f48008b641 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -394,6 +394,7 @@ module ts { // SyntaxKind.VariableDeclaration export interface VariableDeclaration extends Declaration { + parent?: VariableDeclarationList; name: Identifier | BindingPattern; // Declared variable name type?: TypeNode; // Optional type annotation initializer?: Expression; // Optional initializer From 8048163714dc04778dccf838975d147a4346fd2c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Dec 2014 12:36:53 -0800 Subject: [PATCH 49/50] CR feedback. --- src/compiler/utilities.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 62487680759..5925b9b8dac 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -62,22 +62,18 @@ module ts { return node.end - node.pos; } - function hasFlag(val: number, flag: number): boolean { - return (val & flag) !== 0; - } - // Returns true if this node contains a parse error anywhere underneath it. export function containsParseError(node: Node): boolean { aggregateChildData(node); - return hasFlag(node.parserContextFlags, ParserContextFlags.ThisNodeOrAnySubNodesHasError); + return (node.parserContextFlags & ParserContextFlags.ThisNodeOrAnySubNodesHasError) !== 0 } function aggregateChildData(node: Node): void { - if (!hasFlag(node.parserContextFlags, ParserContextFlags.HasAggregatedChildData)) { + 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. - var thisNodeOrAnySubNodesHasError = hasFlag(node.parserContextFlags, ParserContextFlags.ThisNodeHasError) || + var thisNodeOrAnySubNodesHasError = ((node.parserContextFlags & ParserContextFlags.ThisNodeHasError) !== 0) || forEachChild(node, containsParseError); // If so, mark ourselves accordingly. From 4545549e07f2bb36dcaa6697abf8e363929ebc31 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Dec 2014 12:41:08 -0800 Subject: [PATCH 50/50] Disable incremental by default before merging into master. --- src/harness/fourslash.ts | 2 ++ src/services/services.ts | 2 +- tests/cases/unittests/incrementalParser.ts | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index f8086d0ed52..259fcbefac2 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -18,6 +18,8 @@ /// module FourSlash { + ts.disableIncrementalParsing = false; + // Represents a parsed source file with metadata export interface FourSlashFile { // The contents of the file (with markers, etc stripped out) diff --git a/src/services/services.ts b/src/services/services.ts index 757e1b62083..f8070fa0531 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1553,7 +1553,7 @@ module ts { return sourceFile; } - export var disableIncrementalParsing = false; + export var disableIncrementalParsing = true; export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile { if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index 263c396842d..a3875cf8e8c 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -2,6 +2,8 @@ /// module ts { + ts.disableIncrementalParsing = false; + function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { var contents = text.getText(0, text.getLength()); var newContents = contents.substr(0, start) + newText + contents.substring(start + length);