diff --git a/.npmignore b/.npmignore index d3c27afeff4..50ae145423a 100644 --- a/.npmignore +++ b/.npmignore @@ -16,4 +16,5 @@ Jakefile.js .gitattributes .settings/ .travis.yml -.vscode/ \ No newline at end of file +.vscode/ +test.config \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 4a99aaf2237..27b14739d8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ matrix: branches: only: - master + - release-2.5 install: - npm uninstall typescript --no-save diff --git a/netci.groovy b/netci.groovy index bc512f6b245..c60957730c4 100644 --- a/netci.groovy +++ b/netci.groovy @@ -17,6 +17,6 @@ nodeVersions.each { nodeVer -> } Utilities.standardJobSetup(newJob, project, true, "*/${branch}") - Utilities.setMachineAffinity(newJob, 'Ubuntu', '20161020') + Utilities.setMachineAffinity(newJob, 'Ubuntu14.04', '20170821-1') Utilities.addGithubPRTriggerForBranch(newJob, branch, "TypeScript Test Run ${newJobName}") } diff --git a/scripts/buildProtocol.ts b/scripts/buildProtocol.ts index e03338bf60d..c2ac33c83fc 100644 --- a/scripts/buildProtocol.ts +++ b/scripts/buildProtocol.ts @@ -7,11 +7,15 @@ function endsWith(s: string, suffix: string) { return s.lastIndexOf(suffix, s.length - suffix.length) !== -1; } +function isStringEnum(declaration: ts.EnumDeclaration) { + return declaration.members.length && declaration.members.every(m => m.initializer && m.initializer.kind === ts.SyntaxKind.StringLiteral); +} + class DeclarationsWalker { private visitedTypes: ts.Type[] = []; private text = ""; private removedTypes: ts.Type[] = []; - + private constructor(private typeChecker: ts.TypeChecker, private protocolFile: ts.SourceFile) { } @@ -19,7 +23,7 @@ class DeclarationsWalker { let text = "declare namespace ts.server.protocol {\n"; var walker = new DeclarationsWalker(typeChecker, protocolFile); walker.visitTypeNodes(protocolFile); - text = walker.text + text = walker.text ? `declare namespace ts.server.protocol {\n${walker.text}}` : ""; if (walker.removedTypes) { @@ -52,7 +56,7 @@ class DeclarationsWalker { if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") { return; } - if (decl.kind === ts.SyntaxKind.EnumDeclaration) { + if (decl.kind === ts.SyntaxKind.EnumDeclaration && !isStringEnum(decl as ts.EnumDeclaration)) { this.removedTypes.push(type); return; } @@ -91,7 +95,7 @@ class DeclarationsWalker { for (const type of heritageClauses[0].types) { this.processTypeOfNode(type); } - } + } break; } } @@ -110,7 +114,7 @@ class DeclarationsWalker { this.processType(type); } } - } + } } function writeProtocolFile(outputFile: string, protocolTs: string, typeScriptServicesDts: string) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d128eb29d95..495ccbe78d1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1016,7 +1016,18 @@ namespace ts { } } break; - + case SyntaxKind.ExpressionWithTypeArguments: + // The type parameters of a class are not in scope in the base class expression. + if (lastLocation === (location).expression && (location.parent).token === SyntaxKind.ExtendsKeyword) { + const container = location.parent.parent; + if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members, name, meaning & SymbolFlags.Type))) { + if (nameNotFoundMessage) { + error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters); + } + return undefined; + } + } + break; // It is not legal to reference a class's own type parameters from a computed property name that // belongs to the class. For example: // @@ -4929,7 +4940,7 @@ namespace ts { } function resolveBaseTypesOfClass(type: InterfaceType): void { - type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; + type.resolvedBaseTypes = emptyArray; const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { return; @@ -4976,17 +4987,12 @@ namespace ts { error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_a_class_or_interface_type, typeToString(baseType)); return; } - if (type === baseType || hasBaseType(baseType, type)) { + if (type === baseType || hasBaseType(baseType, type)) { error(valueDecl, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); return; } - if (type.resolvedBaseTypes === emptyArray) { - type.resolvedBaseTypes = [baseType]; - } - else { - type.resolvedBaseTypes.push(baseType); - } + type.resolvedBaseTypes = [baseType]; } function areAllOuterTypeParametersApplied(type: Type): boolean { @@ -5003,7 +5009,7 @@ namespace ts { // A valid base type is `any`, any non-generic object type or intersection of non-generic // object types. - function isValidBaseType(type: Type): boolean { + function isValidBaseType(type: Type): type is BaseType { return type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) || type.flags & TypeFlags.Intersection && !forEach((type).types, t => !isValidBaseType(t)); } @@ -5016,12 +5022,12 @@ namespace ts { const baseType = getTypeFromTypeNode(node); if (baseType !== unknownType) { if (isValidBaseType(baseType)) { - if (type !== baseType && !hasBaseType(baseType, type)) { + if (type !== baseType && !hasBaseType(baseType, type)) { if (type.resolvedBaseTypes === emptyArray) { type.resolvedBaseTypes = [baseType]; } else { - type.resolvedBaseTypes.push(baseType); + type.resolvedBaseTypes.push(baseType); } } else { @@ -5657,17 +5663,12 @@ namespace ts { else { // Combinations of function, class, enum and module let members = emptySymbols; - let constructSignatures: Signature[] = emptyArray; let stringIndexInfo: IndexInfo = undefined; if (symbol.exports) { members = getExportsOfSymbol(symbol); } if (symbol.flags & SymbolFlags.Class) { const classType = getDeclaredTypeOfClassOrInterface(symbol); - constructSignatures = getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)); - if (!constructSignatures.length) { - constructSignatures = getDefaultConstructSignatures(classType); - } const baseConstructorType = getBaseConstructorTypeOfClass(classType); if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { members = createSymbolTable(getNamedMembers(members)); @@ -5678,7 +5679,7 @@ namespace ts { } } const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined; - setStructuredTypeMembers(type, members, emptyArray, constructSignatures, stringIndexInfo, numberIndexInfo); + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); // We resolve the members before computing the signatures because a signature may use // typeof with a qualified name expression that circularly references the type we are // in the process of resolving (see issue #6072). The temporarily empty signature list @@ -5686,6 +5687,15 @@ namespace ts { if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { (type).callSignatures = getSignaturesOfSymbol(symbol); } + // And likewise for construct signatures for classes + if (symbol.flags & SymbolFlags.Class) { + const classType = getDeclaredTypeOfClassOrInterface(symbol); + let constructSignatures = getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)); + if (!constructSignatures.length) { + constructSignatures = getDefaultConstructSignatures(classType); + } + (type).constructSignatures = constructSignatures; + } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e7d292d57d1..8121f036cea 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1916,6 +1916,10 @@ "category": "Error", "code": 2561 }, + "Base class expressions cannot reference class type parameters.": { + "category": "Error", + "code": 2562 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c77d267471a..f1a8dfd676b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -647,7 +647,7 @@ namespace ts { } export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile) { - return getLeadingCommentRanges(sourceFileOfNode.text, node.pos); + return node.kind !== SyntaxKind.JsxText ? getLeadingCommentRanges(sourceFileOfNode.text, node.pos) : undefined; } export function getJSDocCommentRanges(node: Node, text: string) { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2b28ecbbfec..32c9fb67da0 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2508,6 +2508,23 @@ namespace FourSlash { } } + public verifySpanOfEnclosingComment(negative: boolean, onlyMultiLineDiverges?: boolean) { + const expected = !negative; + const position = this.currentCaretPosition; + const fileName = this.activeFile.fileName; + const actual = !!this.languageService.getSpanOfEnclosingComment(fileName, position, /*onlyMultiLine*/ false); + const actualOnlyMultiLine = !!this.languageService.getSpanOfEnclosingComment(fileName, position, /*onlyMultiLine*/ true); + if (expected !== actual || onlyMultiLineDiverges === (actual === actualOnlyMultiLine)) { + this.raiseError(`verifySpanOfEnclosingComment failed: + position: '${position}' + fileName: '${fileName}' + onlyMultiLineDiverges: '${onlyMultiLineDiverges}' + actual: '${actual}' + actualOnlyMultiLine: '${actualOnlyMultiLine}' + expected: '${expected}'.`); + } + } + /* Check number of navigationItems which match both searchValue and matchKind, if a filename is passed in, limit the results to that file. @@ -3648,6 +3665,10 @@ namespace FourSlashInterface { this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); } + public isInCommentAtPosition(onlyMultiLineDiverges?: boolean) { + this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLineDiverges); + } + public codeFixAvailable() { this.state.verifyCodeFixAvailable(this.negative); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 5e8d6e76716..78da05570d8 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -487,6 +487,9 @@ namespace Harness.LanguageService { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); } + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { + return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); + } getCodeFixesAtPosition(): ts.CodeAction[] { throw new Error("Not supported on the shim."); } @@ -686,7 +689,7 @@ namespace Harness.LanguageService { this.host.log(message); } - err(message: string): void { + msg(message: string): void { this.host.log(message); } @@ -702,7 +705,8 @@ namespace Harness.LanguageService { return false; } - group() { throw ts.notImplemented(); } + startGroup() { throw ts.notImplemented(); } + endGroup() { throw ts.notImplemented(); } perftrc(message: string): void { return this.host.log(message); diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index a1dc50349e4..cddb9b19d4a 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -90,9 +90,16 @@ namespace RWC { ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile); } - // Load the files + // Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already) + const uniqueNames = ts.createMap(); for (const fileName of fileNames) { - inputFiles.push(getHarnessCompilerInputUnit(fileName)); + // Must maintain order, build result list while checking map + const normalized = ts.normalizeSlashes(fileName); + if (!uniqueNames.has(normalized)) { + uniqueNames.set(normalized, true); + // Load the file + inputFiles.push(getHarnessCompilerInputUnit(fileName)); + } } // Add files to compilation diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 596e080430c..6e548231c91 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -39,8 +39,9 @@ namespace ts.projectSystem { loggingEnabled: () => false, perftrc: noop, info: noop, - err: noop, - group: noop, + msg: noop, + startGroup: noop, + endGroup: noop, getLogFileName: (): string => undefined }; diff --git a/src/server/client.ts b/src/server/client.ts index a7e0615dce8..4acd862a89a 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -527,6 +527,10 @@ namespace ts.server { return notImplemented(); } + getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan { + return notImplemented(); + } + getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] { const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index fe7946fc0f7..cb497ff774f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -958,28 +958,28 @@ namespace ts.server { return; } - this.logger.group(info => { - let counter = 0; - counter = printProjects(this.externalProjects, info, counter); - counter = printProjects(this.configuredProjects, info, counter); - printProjects(this.inferredProjects, info, counter); - - info("Open files: "); - for (const rootFile of this.openFiles) { - info(`\t${rootFile.fileName}`); - } - }); - - function printProjects(projects: Project[], info: (msg: string) => void, counter: number): number { + this.logger.startGroup(); + let counter = 0; + const printProjects = (projects: Project[], counter: number): number => { for (const project of projects) { project.updateGraph(); - info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`); - info(project.filesToString()); - info("-----------------------------------------------"); + this.logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`); + this.logger.info(project.filesToString()); + this.logger.info("-----------------------------------------------"); counter++; } return counter; + }; + counter = printProjects(this.externalProjects, counter); + counter = printProjects(this.configuredProjects, counter); + printProjects(this.inferredProjects, counter); + + this.logger.info("Open files: "); + for (const rootFile of this.openFiles) { + this.logger.info(`\t${rootFile.fileName}`); } + + this.logger.endGroup(); } private findConfiguredProjectByProjectName(configFileName: NormalizedPath) { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 0b7405c7b69..37bf79837c9 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -8,6 +8,7 @@ namespace ts.server.protocol { /* @internal */ BraceFull = "brace-full", BraceCompletion = "braceCompletion", + GetSpanOfEnclosingComment = "getSpanOfEnclosingComment", Change = "change", Close = "close", Completions = "completions", @@ -241,6 +242,21 @@ namespace ts.server.protocol { body?: TodoComment[]; } + /** + * A request to determine if the caret is inside a comment. + */ + export interface SpanOfEnclosingCommentRequest extends FileLocationRequest { + command: CommandTypes.GetSpanOfEnclosingComment; + arguments: SpanOfEnclosingCommentRequestArgs; + } + + export interface SpanOfEnclosingCommentRequestArgs extends FileLocationRequestArgs { + /** + * Requires that the enclosing span be a multi-line comment, or else the request returns undefined. + */ + onlyMultiLine: boolean; + } + /** * Request to obtain outlining spans in file. */ @@ -2473,6 +2489,7 @@ namespace ts.server.protocol { System = "System", ES6 = "ES6", ES2015 = "ES2015", + ESNext = "ESNext" } export const enum ModuleResolutionKind { @@ -2490,5 +2507,8 @@ namespace ts.server.protocol { ES5 = "ES5", ES6 = "ES6", ES2015 = "ES2015", + ES2016 = "ES2016", + ES2017 = "ES2017", + ESNext = "ESNext" } } diff --git a/src/server/server.ts b/src/server/server.ts index 66467fd46ae..0fe37dd8ba0 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -140,6 +140,8 @@ namespace ts.server { class Logger implements server.Logger { private fd = -1; private seq = 0; + private inGroup = false; + private firstInGroup = true; constructor(private readonly logFilename: string, private readonly traceToConsole: boolean, @@ -169,24 +171,24 @@ namespace ts.server { } perftrc(s: string) { - this.msg(s, "Perf"); + this.msg(s, Msg.Perf); } info(s: string) { - this.msg(s, "Info"); + this.msg(s, Msg.Info); } err(s: string) { - this.msg(s, "Err"); + this.msg(s, Msg.Err); } - group(logGroupEntries: (log: (msg: string) => void) => void) { - let firstInGroup = false; - logGroupEntries(s => { - this.msg(s, "Info", /*inGroup*/ true, firstInGroup); - firstInGroup = false; - }); - this.seq++; + startGroup() { + this.inGroup = true; + this.firstInGroup = true; + } + + endGroup() { + this.inGroup = false; } loggingEnabled() { @@ -197,16 +199,16 @@ namespace ts.server { return this.loggingEnabled() && this.level >= level; } - private msg(s: string, type: string, inGroup = false, firstInGroup = false) { + msg(s: string, type: Msg.Types = Msg.Err) { if (!this.canWrite) return; s = `[${nowString()}] ${s}\n`; - if (!inGroup || firstInGroup) { + if (!this.inGroup || this.firstInGroup) { const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " "); s = prefix + s; } this.write(s); - if (!inGroup) { + if (!this.inGroup) { this.seq++; } } diff --git a/src/server/session.ts b/src/server/session.ts index 4122408cfa6..c5b69576811 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -368,7 +368,7 @@ namespace ts.server { msg += "\n" + (err).stack; } } - this.logger.err(msg); + this.logger.msg(msg, Msg.Err); } public send(msg: protocol.Message) { @@ -1025,6 +1025,14 @@ namespace ts.server { return project.getLanguageService(/*ensureSynchronized*/ false).getDocCommentTemplateAtPosition(file, position); } + private getSpanOfEnclosingComment(args: protocol.SpanOfEnclosingCommentRequestArgs) { + const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const onlyMultiLine = args.onlyMultiLine; + const position = this.getPosition(args, scriptInfo); + return project.getLanguageService(/*ensureSynchronized*/ false).getSpanOfEnclosingComment(file, position, onlyMultiLine); + } + private getIndentation(args: protocol.IndentationRequestArgs) { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file)); @@ -1765,6 +1773,9 @@ namespace ts.server { [CommandNames.DocCommentTemplate]: (request: protocol.DocCommentTemplateRequest) => { return this.requiredResponse(this.getDocCommentTemplate(request.arguments)); }, + [CommandNames.GetSpanOfEnclosingComment]: (request: protocol.SpanOfEnclosingCommentRequest) => { + return this.requiredResponse(this.getSpanOfEnclosingComment(request.arguments)); + }, [CommandNames.Format]: (request: protocol.FormatRequest) => { return this.requiredResponse(this.getFormattingEditsForRange(request.arguments)); }, @@ -1947,7 +1958,7 @@ namespace ts.server { return this.executeWithRequestId(request.seq, () => handler(request)); } else { - this.logger.err(`Unrecognized JSON command: ${JSON.stringify(request)}`); + this.logger.msg(`Unrecognized JSON command: ${JSON.stringify(request)}`, Msg.Err); this.output(undefined, CommandNames.Unknown, request.seq, `Unrecognized JSON command: ${request.command}`); return { responseRequired: false }; } diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index de23b2649a6..f80b7a70df6 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -102,7 +102,7 @@ namespace ts.server.typingsInstaller { if (this.log.isEnabled()) { this.log.writeLine(`Updating ${TypesRegistryPackageName} npm package...`); } - this.execSync(`${this.npmPath} install ${TypesRegistryPackageName}`, { cwd: globalTypingsCacheLocation, stdio: "ignore" }); + this.execSync(`${this.npmPath} install --ignore-scripts ${TypesRegistryPackageName}`, { cwd: globalTypingsCacheLocation, stdio: "ignore" }); if (this.log.isEnabled()) { this.log.writeLine(`Updated ${TypesRegistryPackageName} npm package`); } @@ -152,7 +152,7 @@ namespace ts.server.typingsInstaller { if (this.log.isEnabled()) { this.log.writeLine(`#${requestId} with arguments'${JSON.stringify(args)}'.`); } - const command = `${this.npmPath} install ${args.join(" ")} --save-dev --user-agent="typesInstaller/${version}"`; + const command = `${this.npmPath} install --ignore-scripts ${args.join(" ")} --save-dev --user-agent="typesInstaller/${version}"`; const start = Date.now(); let stdout: Buffer; let stderr: Buffer; diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 0d4bc101ff6..e2e2a67eaf1 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -17,11 +17,22 @@ namespace ts.server { loggingEnabled(): boolean; perftrc(s: string): void; info(s: string): void; - err(s: string): void; - group(logGroupEntries: (log: (msg: string) => void) => void): void; + startGroup(): void; + endGroup(): void; + msg(s: string, type?: Msg.Types): void; getLogFileName(): string; } + export namespace Msg { + export type Err = "Err"; + export const Err: Err = "Err"; + export type Info = "Info"; + export const Info: Info = "Info"; + export type Perf = "Perf"; + export const Perf: Perf = "Perf"; + export type Types = Err | Info | Perf; + } + function getProjectRootPath(project: Project): Path { switch (project.projectKind) { case ProjectKind.Configured: @@ -115,9 +126,7 @@ namespace ts.server { } export function createNormalizedPathMap(): NormalizedPathMap { -/* tslint:disable:no-null-keyword */ const map = createMap(); -/* tslint:enable:no-null-keyword */ return { get(path) { return map.get(path); diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index d9a4ca8125a..83f87483194 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1150,6 +1150,56 @@ namespace ts.formatting { } } + /** + * @param precedingToken pass `null` if preceding token was already computed and result was `undefined`. + */ + export function getRangeOfEnclosingComment( + sourceFile: SourceFile, + position: number, + onlyMultiLine: boolean, + precedingToken?: Node | null, // tslint:disable-line:no-null-keyword + tokenAtPosition = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false), + predicate?: (c: CommentRange) => boolean): CommentRange | undefined { + const tokenStart = tokenAtPosition.getStart(sourceFile); + if (tokenStart <= position && position < tokenAtPosition.getEnd()) { + return undefined; + } + + if (precedingToken === undefined) { + precedingToken = findPrecedingToken(position, sourceFile); + } + + // Between two consecutive tokens, all comments are either trailing on the former + // or leading on the latter (and none are in both lists). + const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges(sourceFile.text, precedingToken.end); + const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(tokenAtPosition, sourceFile); + const commentRanges = trailingRangesOfPreviousToken && leadingCommentRangesOfNextToken ? + trailingRangesOfPreviousToken.concat(leadingCommentRangesOfNextToken) : + trailingRangesOfPreviousToken || leadingCommentRangesOfNextToken; + if (commentRanges) { + for (const range of commentRanges) { + // The end marker of a single-line comment does not include the newline character. + // With caret at `^`, in the following case, we are inside a comment (^ denotes the cursor position): + // + // // asdf ^\n + // + // But for closed multi-line comments, we don't want to be inside the comment in the following case: + // + // /* asdf */^ + // + // However, unterminated multi-line comments *do* contain their end. + // + // Internally, we represent the end of the comment at the newline and closing '/', respectively. + // + if ((range.pos < position && position < range.end || + position === range.end && (range.kind === SyntaxKind.SingleLineCommentTrivia || position === sourceFile.getFullWidth()))) { + return (range.kind === SyntaxKind.MultiLineCommentTrivia || !onlyMultiLine) && (!predicate || predicate(range)) ? range : undefined; + } + } + } + return undefined; + } + function getOpenTokenForList(node: Node, list: ReadonlyArray) { switch (node.kind) { case SyntaxKind.Constructor: diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index a98986472f3..4de2e5765e5 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -32,13 +32,36 @@ namespace ts.formatting { } const precedingToken = findPrecedingToken(position, sourceFile); + + const enclosingCommentRange = getRangeOfEnclosingComment(sourceFile, position, /*onlyMultiLine*/ true, precedingToken || null); // tslint:disable-line:no-null-keyword + if (enclosingCommentRange) { + const previousLine = getLineAndCharacterOfPosition(sourceFile, position).line - 1; + const commentStartLine = getLineAndCharacterOfPosition(sourceFile, enclosingCommentRange.pos).line; + + Debug.assert(commentStartLine >= 0); + + if (previousLine <= commentStartLine) { + return findFirstNonWhitespaceColumn(getStartPositionOfLine(commentStartLine, sourceFile), position, sourceFile, options); + } + + const startPostionOfLine = getStartPositionOfLine(previousLine, sourceFile); + const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPostionOfLine, position, sourceFile, options); + + if (column === 0) { + return column; + } + + const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPostionOfLine + character); + return firstNonWhitespaceCharacterCode === CharacterCodes.asterisk ? column - 1 : column; + } + if (!precedingToken) { return getBaseIndentation(options); } // no indentation in string \regex\template literals const precedingTokenIsLiteral = isStringOrRegularExpressionOrTemplateLiteral(precedingToken.kind); - if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && precedingToken.end > position) { + if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && position < precedingToken.end) { return 0; } @@ -405,13 +428,13 @@ namespace ts.formatting { return findFirstNonWhitespaceColumn(lineStart, lineStart + lineAndCharacter.character, sourceFile, options); } - /* - Character is the actual index of the character since the beginning of the line. - Column - position of the character after expanding tabs to spaces - "0\t2$" - value of 'character' for '$' is 3 - value of 'column' for '$' is 6 (assuming that tab size is 4) - */ + /** + * Character is the actual index of the character since the beginning of the line. + * Column - position of the character after expanding tabs to spaces. + * "0\t2$" + * value of 'character' for '$' is 3 + * value of 'column' for '$' is 6 (assuming that tab size is 4) + */ export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) { let character = 0; let column = 0; diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 5099f8b66bd..e0d99d1d271 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -1,13 +1,20 @@ /* @internal */ namespace ts.OutliningElementsCollector { + const collapseText = "..."; + const maxDepth = 20; + export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { const elements: OutliningSpan[] = []; - const collapseText = "..."; + let depth = 0; - function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean) { + walk(sourceFile); + return elements; + + /** If useFullStart is true, then the collapsing span includes leading whitespace, including linebreaks. */ + function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean, useFullStart: boolean) { if (hintSpanNode && startElement && endElement) { const span: OutliningSpan = { - textSpan: createTextSpanFromBounds(startElement.pos, endElement.end), + textSpan: createTextSpanFromBounds(useFullStart ? startElement.getFullStart() : startElement.getStart(), endElement.getEnd()), hintSpan: createTextSpanFromNode(hintSpanNode, sourceFile), bannerText: collapseText, autoCollapse, @@ -82,8 +89,6 @@ namespace ts.OutliningElementsCollector { return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction; } - let depth = 0; - const maxDepth = 20; function walk(n: Node): void { cancellationToken.throwIfCancellationRequested(); if (depth > maxDepth) { @@ -113,7 +118,7 @@ namespace ts.OutliningElementsCollector { parent.kind === SyntaxKind.WithStatement || parent.kind === SyntaxKind.CatchClause) { - addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n)); + addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true); break; } @@ -121,13 +126,13 @@ namespace ts.OutliningElementsCollector { // Could be the try-block, or the finally-block. const tryStatement = parent; if (tryStatement.tryBlock === n) { - addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n)); + addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true); break; } else if (tryStatement.finallyBlock === n) { const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile); if (finallyKeyword) { - addOutliningSpan(finallyKeyword, openBrace, closeBrace, autoCollapse(n)); + addOutliningSpan(finallyKeyword, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true); break; } } @@ -151,31 +156,35 @@ namespace ts.OutliningElementsCollector { case SyntaxKind.ModuleBlock: { const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile); const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile); - addOutliningSpan(n.parent, openBrace, closeBrace, autoCollapse(n)); + addOutliningSpan(n.parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true); break; } case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: - case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.CaseBlock: { const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile); const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile); - addOutliningSpan(n, openBrace, closeBrace, autoCollapse(n)); + addOutliningSpan(n, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true); break; } + // If the block has no leading keywords and is inside an array literal, + // we only want to collapse the span of the block. + // Otherwise, the collapsed section will include the end of the previous line. + case SyntaxKind.ObjectLiteralExpression: + const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile); + const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile); + addOutliningSpan(n, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ !isArrayLiteralExpression(n.parent)); + break; case SyntaxKind.ArrayLiteralExpression: const openBracket = findChildOfKind(n, SyntaxKind.OpenBracketToken, sourceFile); const closeBracket = findChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile); - addOutliningSpan(n, openBracket, closeBracket, autoCollapse(n)); + addOutliningSpan(n, openBracket, closeBracket, autoCollapse(n), /*useFullStart*/ !isArrayLiteralExpression(n.parent)); break; } depth++; forEachChild(n, walk); depth--; } - - walk(sourceFile); - return elements; } } \ No newline at end of file diff --git a/src/services/refactors/extractMethod.ts b/src/services/refactors/extractMethod.ts index cf58ede99bd..90e33041695 100644 --- a/src/services/refactors/extractMethod.ts +++ b/src/services/refactors/extractMethod.ts @@ -856,7 +856,7 @@ namespace ts.refactor.extractMethod { Write = 2 } - interface UsageEntry { + export interface UsageEntry { readonly usage: Usage; readonly symbol: Symbol; readonly node: Node; diff --git a/src/services/services.ts b/src/services/services.ts index ed9732cc9cf..7354e706cc9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -107,12 +107,14 @@ namespace ts { scanner.setTextPos(pos); while (pos < end) { const token = scanner.scan(); - Debug.assert(token !== SyntaxKind.EndOfFileToken); // Else it would infinitely loop const textPos = scanner.getTextPos(); if (textPos <= end) { nodes.push(createNode(token, pos, textPos, this)); } pos = textPos; + if (token === SyntaxKind.EndOfFileToken) { + break; + } } return pos; } @@ -1757,17 +1759,20 @@ namespace ts { function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const settings = toEditorSettings(options); - if (key === "{") { - return formatting.formatOnOpeningCurly(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === "}") { - return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === ";") { - return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === "\n") { - return formatting.formatOnEnter(position, sourceFile, getRuleProvider(settings), settings); + + if (!isInComment(sourceFile, position)) { + if (key === "{") { + return formatting.formatOnOpeningCurly(position, sourceFile, getRuleProvider(settings), settings); + } + else if (key === "}") { + return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(settings), settings); + } + else if (key === ";") { + return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(settings), settings); + } + else if (key === "\n") { + return formatting.formatOnEnter(position, sourceFile, getRuleProvider(settings), settings); + } } return []; @@ -1826,6 +1831,12 @@ namespace ts { return true; } + function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean) { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const range = ts.formatting.getRangeOfEnclosingComment(sourceFile, position, onlyMultiLine); + return range && createTextSpanFromRange(range); + } + function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { // Note: while getting todo comments seems like a syntactic operation, we actually // treat it as a semantic operation here. This is because we expect our host to call @@ -2050,6 +2061,7 @@ namespace ts { getFormattingEditsAfterKeystroke, getDocCommentTemplateAtPosition, isValidBraceCompletionAtPosition, + getSpanOfEnclosingComment, getCodeFixesAtPosition, getEmitOutput, getNonBoundSourceFile, diff --git a/src/services/shims.ts b/src/services/shims.ts index 4bfe3590082..40d4ff2e935 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -254,6 +254,11 @@ namespace ts { */ isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; + /** + * Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists. + */ + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string; + getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; } @@ -815,6 +820,13 @@ namespace ts { ); } + public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string { + return this.forwardJSONCall( + `getSpanOfEnclosingComment('${fileName}', ${position})`, + () => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine) + ); + } + /// GET SMART INDENT public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string { return this.forwardJSONCall( diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 303bb8395ee..05c451cc3f3 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -111,124 +111,123 @@ namespace ts.SymbolDisplay { let signature: Signature; type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol.exportSymbol || symbol, location); - if (type) { - if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { - const right = (location.parent).name; - // Either the location is on the right of a property access, or on the left and the right is missing - if (right === location || (right && right.getFullWidth() === 0)) { - location = location.parent; + + if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { + const right = (location.parent).name; + // Either the location is on the right of a property access, or on the left and the right is missing + if (right === location || (right && right.getFullWidth() === 0)) { + location = location.parent; + } + } + + // try get the call/construct signature from the type if it matches + let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement; + if (isCallOrNewExpression(location)) { + callExpressionLike = location; + } + else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) { + callExpressionLike = location.parent; + } + else if (location.parent && isJsxOpeningLikeElement(location.parent) && isFunctionLike(symbol.valueDeclaration)) { + callExpressionLike = location.parent; + } + + if (callExpressionLike) { + const candidateSignatures: Signature[] = []; + signature = typeChecker.getResolvedSignature(callExpressionLike, candidateSignatures); + if (!signature && candidateSignatures.length) { + // Use the first candidate: + signature = candidateSignatures[0]; + } + + const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword); + + const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures(); + + if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) { + // Get the first signature if there is one -- allSignatures may contain + // either the original signature or its target, so check for either + signature = allSignatures.length ? allSignatures[0] : undefined; + } + + if (signature) { + if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) { + // Constructor + symbolKind = ScriptElementKind.constructorImplementationElement; + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); } - } - - // try get the call/construct signature from the type if it matches - let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement; - if (isCallOrNewExpression(location)) { - callExpressionLike = location; - } - else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) { - callExpressionLike = location.parent; - } - else if (location.parent && isJsxOpeningLikeElement(location.parent) && isFunctionLike(symbol.valueDeclaration)) { - callExpressionLike = location.parent; - } - - if (callExpressionLike) { - const candidateSignatures: Signature[] = []; - signature = typeChecker.getResolvedSignature(callExpressionLike, candidateSignatures); - if (!signature && candidateSignatures.length) { - // Use the first candidate: - signature = candidateSignatures[0]; - } - - const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword); - - const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures(); - - if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) { - // Get the first signature if there is one -- allSignatures may contain - // either the original signature or its target, so check for either - signature = allSignatures.length ? allSignatures[0] : undefined; - } - - if (signature) { - if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) { - // Constructor - symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + else if (symbolFlags & SymbolFlags.Alias) { + symbolKind = ScriptElementKind.alias; + pushTypePart(symbolKind); + displayParts.push(spacePart()); + if (useConstructSignatures) { + displayParts.push(keywordPart(SyntaxKind.NewKeyword)); + displayParts.push(spacePart()); } - else if (symbolFlags & SymbolFlags.Alias) { - symbolKind = ScriptElementKind.alias; - pushTypePart(symbolKind); + addFullSymbolName(symbol); + } + else { + addPrefixForAnyFunctionOrVar(symbol, symbolKind); + } + + switch (symbolKind) { + case ScriptElementKind.jsxAttribute: + case ScriptElementKind.memberVariableElement: + case ScriptElementKind.variableElement: + case ScriptElementKind.constElement: + case ScriptElementKind.letElement: + case ScriptElementKind.parameterElement: + case ScriptElementKind.localVariableElement: + // If it is call or construct signature of lambda's write type name + displayParts.push(punctuationPart(SyntaxKind.ColonToken)); displayParts.push(spacePart()); if (useConstructSignatures) { displayParts.push(keywordPart(SyntaxKind.NewKeyword)); displayParts.push(spacePart()); } - addFullSymbolName(symbol); - } - else { - addPrefixForAnyFunctionOrVar(symbol, symbolKind); - } + if (!(type.flags & TypeFlags.Object && (type).objectFlags & ObjectFlags.Anonymous) && type.symbol) { + addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments)); + } + addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature); + break; - switch (symbolKind) { - case ScriptElementKind.jsxAttribute: - case ScriptElementKind.memberVariableElement: - case ScriptElementKind.variableElement: - case ScriptElementKind.constElement: - case ScriptElementKind.letElement: - case ScriptElementKind.parameterElement: - case ScriptElementKind.localVariableElement: - // If it is call or construct signature of lambda's write type name - displayParts.push(punctuationPart(SyntaxKind.ColonToken)); - displayParts.push(spacePart()); - if (useConstructSignatures) { - displayParts.push(keywordPart(SyntaxKind.NewKeyword)); - displayParts.push(spacePart()); - } - if (!(type.flags & TypeFlags.Object && (type).objectFlags & ObjectFlags.Anonymous) && type.symbol) { - addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments)); - } - addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature); - break; - - default: - // Just signature - addSignatureDisplayParts(signature, allSignatures); - } - hasAddedSymbolInfo = true; + default: + // Just signature + addSignatureDisplayParts(signature, allSignatures); } + hasAddedSymbolInfo = true; } - else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration - (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration - // get the signature from the declaration and write it - const functionDeclaration = location.parent; - // Use function declaration to write the signatures only if the symbol corresponding to this declaration - const locationIsSymbolDeclaration = find(symbol.declarations, declaration => - declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); + } + else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration + (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration + // get the signature from the declaration and write it + const functionDeclaration = location.parent; + // Use function declaration to write the signatures only if the symbol corresponding to this declaration + const locationIsSymbolDeclaration = find(symbol.declarations, declaration => + declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); - if (locationIsSymbolDeclaration) { - const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); - if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { - signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); - } - else { - signature = allSignatures[0]; - } - - if (functionDeclaration.kind === SyntaxKind.Constructor) { - // show (constructor) Type(...) signature - symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); - } - else { - // (function/method) symbol(..signature) - addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && - !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); - } - - addSignatureDisplayParts(signature, allSignatures); - hasAddedSymbolInfo = true; + if (locationIsSymbolDeclaration) { + const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); + if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { + signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); } + else { + signature = allSignatures[0]; + } + + if (functionDeclaration.kind === SyntaxKind.Constructor) { + // show (constructor) Type(...) signature + symbolKind = ScriptElementKind.constructorImplementationElement; + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + } + else { + // (function/method) symbol(..signature) + addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && + !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); + } + + addSignatureDisplayParts(signature, allSignatures); + hasAddedSymbolInfo = true; } } } @@ -417,7 +416,9 @@ namespace ts.SymbolDisplay { symbolFlags & SymbolFlags.Accessor || symbolKind === ScriptElementKind.memberFunctionElement) { const allSignatures = type.getNonNullableType().getCallSignatures(); - addSignatureDisplayParts(allSignatures[0], allSignatures); + if (allSignatures.length) { + addSignatureDisplayParts(allSignatures[0], allSignatures); + } } } } diff --git a/src/services/types.ts b/src/services/types.ts index b034e2813c6..f4f323b8e9f 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -270,6 +270,8 @@ namespace ts { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 27215190db3..e8f01bb0785 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -720,8 +720,14 @@ namespace ts { } } - export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, includeJsDoc?: boolean): Node { - return find(startNode || sourceFile); + /** + * Finds the rightmost token satisfying `token.end <= position`, + * excluding `JsxText` tokens containing only whitespace. + */ + export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, includeJsDoc?: boolean): Node | undefined { + const result = find(startNode || sourceFile); + Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result))); + return result; function findRightmostToken(n: Node): Node { if (isToken(n)) { @@ -742,19 +748,17 @@ namespace ts { const children = n.getChildren(); for (let i = 0; i < children.length; i++) { const child = children[i]; - // condition 'position < child.end' checks if child node end after the position - // in the example below this condition will be false for 'aaaa' and 'bbbb' and true for 'ccc' - // aaaa___bbbb___$__ccc - // after we found child node with end after the position we check if start of the node is after the position. - // if yes - then position is in the trivia and we need to look into the previous child to find the token in question. - // if no - position is in the node itself so we should recurse in it. - // NOTE: JsxText is a weird kind of node that can contain only whitespaces (since they are not counted as trivia). - // if this is the case - then we should assume that token in question is located in previous child. - if (position < child.end && (nodeHasTokens(child) || child.kind === SyntaxKind.JsxText)) { + // Note that the span of a node's tokens is [node.getStart(...), node.end). + // Given that `position < child.end` and child has constituent tokens, we distinguish these cases: + // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`): + // we need to find the last token in a previous child. + // 2) `position` is within the same span: we recurse on `child`. + if (position < child.end) { const start = child.getStart(sourceFile, includeJsDoc); const lookInPreviousChild = (start >= position) || // cursor in the leading trivia - (child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText + !nodeHasTokens(child) || + isWhiteSpaceOnlyJsxText(child); if (lookInPreviousChild) { // actual start of the node is past the position - previous token should be at the end of previous child @@ -780,10 +784,17 @@ namespace ts { } } - /// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition' + /** + * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. + */ function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node { for (let i = exclusiveStartPosition - 1; i >= 0; i--) { - if (nodeHasTokens(children[i])) { + const child = children[i]; + + if (isWhiteSpaceOnlyJsxText(child)) { + Debug.assert(i > 0, "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); + } + else if (nodeHasTokens(children[i])) { return children[i]; } } @@ -851,6 +862,10 @@ namespace ts { return false; } + export function isWhiteSpaceOnlyJsxText(node: Node): node is JsxText { + return isJsxText(node) && node.containsOnlyWhiteSpaces; + } + export function isInTemplateString(sourceFile: SourceFile, position: number) { const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); @@ -863,41 +878,11 @@ namespace ts { * @param predicate Additional predicate to test on the comment range. */ export function isInComment( - sourceFile: SourceFile, - position: number, - tokenAtPosition = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false), - predicate?: (c: CommentRange) => boolean): boolean { - return position <= tokenAtPosition.getStart(sourceFile) && - (isInCommentRange(getLeadingCommentRanges(sourceFile.text, tokenAtPosition.pos)) || - isInCommentRange(getTrailingCommentRanges(sourceFile.text, tokenAtPosition.pos))); - - function isInCommentRange(commentRanges: CommentRange[]): boolean { - return forEach(commentRanges, c => isPositionInCommentRange(c, position, sourceFile.text) && (!predicate || predicate(c))); - } - } - - function isPositionInCommentRange({ pos, end, kind }: ts.CommentRange, position: number, text: string): boolean { - if (pos < position && position < end) { - return true; - } - else if (position === end) { - // The end marker of a single-line comment does not include the newline character. - // In the following case, we are inside a comment (^ denotes the cursor position): - // - // // asdf ^\n - // - // But for multi-line comments, we don't want to be inside the comment in the following case: - // - // /* asdf */^ - // - // Internally, we represent the end of the comment at the newline and closing '/', respectively. - return kind === SyntaxKind.SingleLineCommentTrivia || - // true for unterminated multi-line comment - !(text.charCodeAt(end - 1) === CharacterCodes.slash && text.charCodeAt(end - 2) === CharacterCodes.asterisk); - } - else { - return false; - } + sourceFile: SourceFile, + position: number, + tokenAtPosition?: Node, + predicate?: (c: CommentRange) => boolean): boolean { + return !!formatting.getRangeOfEnclosingComment(sourceFile, position, /*onlyMultiLine*/ false, /*precedingToken*/ undefined, tokenAtPosition, predicate); } export function hasDocComment(sourceFile: SourceFile, position: number) { @@ -916,7 +901,7 @@ namespace ts { function nodeHasTokens(n: Node): boolean { // If we have a token or node that has a non-zero width, it must have tokens. - // Note, that getWidth() does not take trivia into account. + // Note: getWidth() does not take trivia into account. return n.getWidth() !== 0; } diff --git a/tests/baselines/reference/baseExpressionTypeParameters.errors.txt b/tests/baselines/reference/baseExpressionTypeParameters.errors.txt new file mode 100644 index 00000000000..4fa8f2f6d3d --- /dev/null +++ b/tests/baselines/reference/baseExpressionTypeParameters.errors.txt @@ -0,0 +1,19 @@ +tests/cases/compiler/baseExpressionTypeParameters.ts(10,27): error TS2561: Base class expressions cannot reference class type parameters. + + +==== tests/cases/compiler/baseExpressionTypeParameters.ts (1 errors) ==== + // Repro from #17829 + + function base() { + class Base { + static prop: T; + } + return Base; + } + + class Gen extends base() {} // Error, T not in scope + ~ +!!! error TS2561: Base class expressions cannot reference class type parameters. + class Spec extends Gen {} + + Spec.prop; \ No newline at end of file diff --git a/tests/baselines/reference/baseExpressionTypeParameters.js b/tests/baselines/reference/baseExpressionTypeParameters.js new file mode 100644 index 00000000000..a3a77cbc6e3 --- /dev/null +++ b/tests/baselines/reference/baseExpressionTypeParameters.js @@ -0,0 +1,50 @@ +//// [baseExpressionTypeParameters.ts] +// Repro from #17829 + +function base() { + class Base { + static prop: T; + } + return Base; +} + +class Gen extends base() {} // Error, T not in scope +class Spec extends Gen {} + +Spec.prop; + +//// [baseExpressionTypeParameters.js] +// Repro from #17829 +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +function base() { + var Base = /** @class */ (function () { + function Base() { + } + return Base; + }()); + return Base; +} +var Gen = /** @class */ (function (_super) { + __extends(Gen, _super); + function Gen() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Gen; +}(base())); // Error, T not in scope +var Spec = /** @class */ (function (_super) { + __extends(Spec, _super); + function Spec() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Spec; +}(Gen)); +Spec.prop; diff --git a/tests/baselines/reference/cloduleGenericOnSelfMember.js b/tests/baselines/reference/cloduleGenericOnSelfMember.js new file mode 100644 index 00000000000..972651f1045 --- /dev/null +++ b/tests/baselines/reference/cloduleGenericOnSelfMember.js @@ -0,0 +1,42 @@ +//// [cloduleGenericOnSelfMember.ts] +class ServiceBase { + field: T; +} +class Service extends ServiceBase { +} +namespace Service { + export const Base = { + name: "1", + value: 5 + }; +} + +//// [cloduleGenericOnSelfMember.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var ServiceBase = /** @class */ (function () { + function ServiceBase() { + } + return ServiceBase; +}()); +var Service = /** @class */ (function (_super) { + __extends(Service, _super); + function Service() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Service; +}(ServiceBase)); +(function (Service) { + Service.Base = { + name: "1", + value: 5 + }; +})(Service || (Service = {})); diff --git a/tests/baselines/reference/cloduleGenericOnSelfMember.symbols b/tests/baselines/reference/cloduleGenericOnSelfMember.symbols new file mode 100644 index 00000000000..c7e0126fa7a --- /dev/null +++ b/tests/baselines/reference/cloduleGenericOnSelfMember.symbols @@ -0,0 +1,30 @@ +=== tests/cases/compiler/cloduleGenericOnSelfMember.ts === +class ServiceBase { +>ServiceBase : Symbol(ServiceBase, Decl(cloduleGenericOnSelfMember.ts, 0, 0)) +>T : Symbol(T, Decl(cloduleGenericOnSelfMember.ts, 0, 18)) + + field: T; +>field : Symbol(ServiceBase.field, Decl(cloduleGenericOnSelfMember.ts, 0, 22)) +>T : Symbol(T, Decl(cloduleGenericOnSelfMember.ts, 0, 18)) +} +class Service extends ServiceBase { +>Service : Symbol(Service, Decl(cloduleGenericOnSelfMember.ts, 2, 1), Decl(cloduleGenericOnSelfMember.ts, 4, 1)) +>ServiceBase : Symbol(ServiceBase, Decl(cloduleGenericOnSelfMember.ts, 0, 0)) +>Service.Base : Symbol(Service.Base, Decl(cloduleGenericOnSelfMember.ts, 6, 16)) +>Service : Symbol(Service, Decl(cloduleGenericOnSelfMember.ts, 2, 1), Decl(cloduleGenericOnSelfMember.ts, 4, 1)) +>Base : Symbol(Service.Base, Decl(cloduleGenericOnSelfMember.ts, 6, 16)) +} +namespace Service { +>Service : Symbol(Service, Decl(cloduleGenericOnSelfMember.ts, 2, 1), Decl(cloduleGenericOnSelfMember.ts, 4, 1)) + + export const Base = { +>Base : Symbol(Base, Decl(cloduleGenericOnSelfMember.ts, 6, 16)) + + name: "1", +>name : Symbol(name, Decl(cloduleGenericOnSelfMember.ts, 6, 25)) + + value: 5 +>value : Symbol(value, Decl(cloduleGenericOnSelfMember.ts, 7, 18)) + + }; +} diff --git a/tests/baselines/reference/cloduleGenericOnSelfMember.types b/tests/baselines/reference/cloduleGenericOnSelfMember.types new file mode 100644 index 00000000000..87590745c6f --- /dev/null +++ b/tests/baselines/reference/cloduleGenericOnSelfMember.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/cloduleGenericOnSelfMember.ts === +class ServiceBase { +>ServiceBase : ServiceBase +>T : T + + field: T; +>field : T +>T : T +} +class Service extends ServiceBase { +>Service : Service +>ServiceBase : ServiceBase<{ name: string; value: number; }> +>Service.Base : { name: string; value: number; } +>Service : typeof Service +>Base : { name: string; value: number; } +} +namespace Service { +>Service : typeof Service + + export const Base = { +>Base : { name: string; value: number; } +>{ name: "1", value: 5 } : { name: string; value: number; } + + name: "1", +>name : string +>"1" : "1" + + value: 5 +>value : number +>5 : 5 + + }; +} diff --git a/tests/baselines/reference/inheritFromGenericTypeParameter.errors.txt b/tests/baselines/reference/inheritFromGenericTypeParameter.errors.txt index 08d138f2e5d..c043e37575c 100644 --- a/tests/baselines/reference/inheritFromGenericTypeParameter.errors.txt +++ b/tests/baselines/reference/inheritFromGenericTypeParameter.errors.txt @@ -1,11 +1,11 @@ -tests/cases/compiler/inheritFromGenericTypeParameter.ts(1,20): error TS2693: 'T' only refers to a type, but is being used as a value here. +tests/cases/compiler/inheritFromGenericTypeParameter.ts(1,20): error TS2304: Cannot find name 'T'. tests/cases/compiler/inheritFromGenericTypeParameter.ts(2,24): error TS2312: An interface may only extend a class or another interface. ==== tests/cases/compiler/inheritFromGenericTypeParameter.ts (2 errors) ==== class C extends T { } ~ -!!! error TS2693: 'T' only refers to a type, but is being used as a value here. +!!! error TS2304: Cannot find name 'T'. interface I extends T { } ~ !!! error TS2312: An interface may only extend a class or another interface. \ No newline at end of file diff --git a/tests/baselines/reference/typeParameterAsBaseClass.errors.txt b/tests/baselines/reference/typeParameterAsBaseClass.errors.txt index e633a6dd396..dec7f7f2a4a 100644 --- a/tests/baselines/reference/typeParameterAsBaseClass.errors.txt +++ b/tests/baselines/reference/typeParameterAsBaseClass.errors.txt @@ -1,11 +1,11 @@ -tests/cases/compiler/typeParameterAsBaseClass.ts(1,20): error TS2693: 'T' only refers to a type, but is being used as a value here. +tests/cases/compiler/typeParameterAsBaseClass.ts(1,20): error TS2304: Cannot find name 'T'. tests/cases/compiler/typeParameterAsBaseClass.ts(2,24): error TS2422: A class may only implement another class or interface. ==== tests/cases/compiler/typeParameterAsBaseClass.ts (2 errors) ==== class C extends T {} ~ -!!! error TS2693: 'T' only refers to a type, but is being used as a value here. +!!! error TS2304: Cannot find name 'T'. class C2 implements T {} ~ !!! error TS2422: A class may only implement another class or interface. \ No newline at end of file diff --git a/tests/baselines/reference/typeParameterAsBaseType.errors.txt b/tests/baselines/reference/typeParameterAsBaseType.errors.txt index c16b16d0b1a..835b2369948 100644 --- a/tests/baselines/reference/typeParameterAsBaseType.errors.txt +++ b/tests/baselines/reference/typeParameterAsBaseType.errors.txt @@ -1,5 +1,5 @@ -tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(4,20): error TS2693: 'T' only refers to a type, but is being used as a value here. -tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(5,24): error TS2693: 'U' only refers to a type, but is being used as a value here. +tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(4,20): error TS2304: Cannot find name 'T'. +tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(5,24): error TS2304: Cannot find name 'U'. tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(7,24): error TS2312: An interface may only extend a class or another interface. tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(8,28): error TS2312: An interface may only extend a class or another interface. @@ -10,10 +10,10 @@ tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(8,28): e class C extends T { } ~ -!!! error TS2693: 'T' only refers to a type, but is being used as a value here. +!!! error TS2304: Cannot find name 'T'. class C2 extends U { } ~ -!!! error TS2693: 'U' only refers to a type, but is being used as a value here. +!!! error TS2304: Cannot find name 'U'. interface I extends T { } ~ diff --git a/tests/cases/compiler/baseExpressionTypeParameters.ts b/tests/cases/compiler/baseExpressionTypeParameters.ts new file mode 100644 index 00000000000..ca864f9a7f0 --- /dev/null +++ b/tests/cases/compiler/baseExpressionTypeParameters.ts @@ -0,0 +1,13 @@ +// Repro from #17829 + +function base() { + class Base { + static prop: T; + } + return Base; +} + +class Gen extends base() {} // Error, T not in scope +class Spec extends Gen {} + +Spec.prop; \ No newline at end of file diff --git a/tests/cases/compiler/cloduleGenericOnSelfMember.ts b/tests/cases/compiler/cloduleGenericOnSelfMember.ts new file mode 100644 index 00000000000..23f0e6af1b6 --- /dev/null +++ b/tests/cases/compiler/cloduleGenericOnSelfMember.ts @@ -0,0 +1,11 @@ +class ServiceBase { + field: T; +} +class Service extends ServiceBase { +} +namespace Service { + export const Base = { + name: "1", + value: 5 + }; +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionListAndMemberListOnCommentedDot.ts b/tests/cases/fourslash/completionListAndMemberListOnCommentedDot.ts index d0517d87279..b57e7b84b53 100644 --- a/tests/cases/fourslash/completionListAndMemberListOnCommentedDot.ts +++ b/tests/cases/fourslash/completionListAndMemberListOnCommentedDot.ts @@ -14,5 +14,4 @@ //////c./**/ goTo.marker(); -verify.completionListIsEmpty(); verify.completionListIsEmpty(); \ No newline at end of file diff --git a/tests/cases/fourslash/formatOnEnterInComment.ts b/tests/cases/fourslash/formatOnEnterInComment.ts new file mode 100644 index 00000000000..489eb6ac6ce --- /dev/null +++ b/tests/cases/fourslash/formatOnEnterInComment.ts @@ -0,0 +1,14 @@ +/// + +//// /** +//// * /*1*/ +//// */ + +goTo.marker("1"); +edit.insertLine(""); +verify.currentFileContentIs( +` /** + * + + */` +); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 78c62b71eac..abb2c38c6b4 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -153,6 +153,7 @@ declare namespace FourSlashInterface { typeDefinitionCountIs(expectedCount: number): void; implementationListIsEmpty(): void; isValidBraceCompletionAtPosition(openingBrace?: string): void; + isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void; codeFixAvailable(): void; applicableRefactorAvailableAtMarker(markerName: string): void; codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void; diff --git a/tests/cases/fourslash/getOutliningForObjectsInArray.ts b/tests/cases/fourslash/getOutliningForObjectsInArray.ts new file mode 100644 index 00000000000..89634224832 --- /dev/null +++ b/tests/cases/fourslash/getOutliningForObjectsInArray.ts @@ -0,0 +1,56 @@ +/// + +// objects in x should generate outlining spans that do not render in VS +//// const x =[| [ +//// [|{ a: 0 }|], +//// [|{ b: 1 }|], +//// [|{ c: 2 }|] +//// ]|]; +//// +// objects in y should generate outlining spans that render as expected +//// const y =[| [ +//// [|{ +//// a: 0 +//// }|], +//// [|{ +//// b: 1 +//// }|], +//// [|{ +//// c: 2 +//// }|] +//// ]|]; +//// +// same behavior for nested arrays +//// const w =[| [ +//// [|[ 0 ]|], +//// [|[ 1 ]|], +//// [|[ 2 ]|] +//// ]|]; +//// +//// const z =[| [ +//// [|[ +//// 0 +//// ]|], +//// [|[ +//// 1 +//// ]|], +//// [|[ +//// 2 +//// ]|] +//// ]|]; +//// +// multiple levels of nesting work as expected +//// const z =[| [ +//// [|[ +//// [|{ hello: 0 }|] +//// ]|], +//// [|[ +//// [|{ hello: 3 }|] +//// ]|], +//// [|[ +//// [|{ hello: 5 }|], +//// [|{ hello: 7 }|] +//// ]|] +//// ]|]; + +verify.outliningSpansInCurrentFile(test.ranges()); \ No newline at end of file diff --git a/tests/cases/fourslash/indentionsOfCommentBlockAfterFormatting.ts b/tests/cases/fourslash/indentationInBlockCommentAfterFormatting.ts similarity index 88% rename from tests/cases/fourslash/indentionsOfCommentBlockAfterFormatting.ts rename to tests/cases/fourslash/indentationInBlockCommentAfterFormatting.ts index 3b686285b99..b965b314234 100644 --- a/tests/cases/fourslash/indentionsOfCommentBlockAfterFormatting.ts +++ b/tests/cases/fourslash/indentationInBlockCommentAfterFormatting.ts @@ -27,13 +27,13 @@ verify.indentationIs(4); goTo.marker("2"); verify.indentationIs(4); goTo.marker("3"); -verify.indentationIs(4); +verify.indentationIs(3); goTo.marker("4"); -verify.indentationIs(4); +verify.indentationIs(3); // Putting a marker in line "*" would bring some error when parsing code in automation. // So move right by 1 offset from marker 4 to locate the caret in this line. edit.moveRight(1); -verify.indentationIs(4); +verify.indentationIs(3); // Putting a marker in line " */" would bring some error when parsing code in automation. // So move left by 1 offset from marker 5 to locate the caret in this line. goTo.marker("5"); diff --git a/tests/cases/fourslash/indentationInComments.ts b/tests/cases/fourslash/indentationInComments.ts new file mode 100644 index 00000000000..d54ec9daacb --- /dev/null +++ b/tests/cases/fourslash/indentationInComments.ts @@ -0,0 +1,32 @@ +/// + +//// // /*0_0*/ +//// /* /*0_1*/ +//// some text /*0_2*/ +//// some text /*1_0*/ +//// * some text /*0_3*/ +//// /*0_4*/ +//// */ +//// function foo() { +//// // /*4_0*/ +//// /** /*4_1*/ +//// * /*4_2*/ +//// * /*4_3*/ +//// /*7_0*/ +//// */ +//// /* /*4_4*/ */ +//// } + +for (let i = 0; i < 5; ++i) { + goTo.marker(`0_${i}`); + verify.indentationIs(0); + + goTo.marker(`4_${i}`); + verify.indentationIs(4); +} + +goTo.marker(`1_0`); +verify.indentationIs(1); + +goTo.marker(`7_0`); +verify.indentationIs(7); diff --git a/tests/cases/fourslash/isInMultiLineComment.ts b/tests/cases/fourslash/isInMultiLineComment.ts new file mode 100644 index 00000000000..96da4f0021f --- /dev/null +++ b/tests/cases/fourslash/isInMultiLineComment.ts @@ -0,0 +1,46 @@ +/// + +//// /* x */ +//// /** +//// * @param this doesn't make sense here. +//// */ +//// // x +//// let x = 1; /* +//// * + +const firstCommentStart = 0; +const firstCommentEnd = 7; +goTo.position(firstCommentStart); +verify.not.isInCommentAtPosition(); + +goTo.position(firstCommentStart + 1); +verify.isInCommentAtPosition(); +goTo.position(firstCommentEnd - 1); +verify.isInCommentAtPosition(); + +goTo.position(firstCommentEnd); +verify.not.isInCommentAtPosition(); + +const multilineJsDocStart = firstCommentEnd + 1; +const multilineJsDocEnd = multilineJsDocStart + 49; + +goTo.position(multilineJsDocStart); +verify.not.isInCommentAtPosition(); +goTo.position(multilineJsDocStart + 1); +verify.isInCommentAtPosition(); +goTo.position(multilineJsDocEnd - 1); +verify.isInCommentAtPosition(); +goTo.position(multilineJsDocEnd); +verify.not.isInCommentAtPosition(); + +const singleLineCommentStart = multilineJsDocEnd + 1; + +goTo.position(singleLineCommentStart + 1); +verify.isInCommentAtPosition(/*onlyMultiLineDiverges*/ true); + +const postNodeCommentStart = singleLineCommentStart + 16; + +goTo.position(postNodeCommentStart); +verify.not.isInCommentAtPosition(); +goTo.position(postNodeCommentStart + 1); +verify.isInCommentAtPosition(); diff --git a/tests/cases/fourslash/isInMultiLineCommentInJsxText.ts b/tests/cases/fourslash/isInMultiLineCommentInJsxText.ts new file mode 100644 index 00000000000..1664ca0cd1b --- /dev/null +++ b/tests/cases/fourslash/isInMultiLineCommentInJsxText.ts @@ -0,0 +1,27 @@ +/// + +// @Filename: file.jsx +////
+//// // /*0*/ +//// /* /*1*/ */ +//// /** +//// * /*2*/ +//// */ +//// foo() /* /*3*/ */ +//// // /*4*/ +//// /* /*5*/ */ +//// /** +//// * /*6*/ +//// */ +////
+////
+//// // /*7*/ +//// /* /*8*/ */ +//// /** +//// * /*9*/ +//// */ + +for (let i = 0; i < 10; ++i) { + goTo.marker(i.toString()); + verify.not.isInCommentAtPosition(); +} diff --git a/tests/cases/fourslash/isInMultiLineCommentInTemplateLiteral.ts b/tests/cases/fourslash/isInMultiLineCommentInTemplateLiteral.ts new file mode 100644 index 00000000000..c3e2ae92e8a --- /dev/null +++ b/tests/cases/fourslash/isInMultiLineCommentInTemplateLiteral.ts @@ -0,0 +1,27 @@ +/// + +// @Filename: file.jsx +//// ` +//// // /*0*/ +//// /* /*1*/ */ +//// /** +//// * /*2*/ +//// */ +//// foo() +//// // /*3*/ +//// /* /*4*/ */ +//// /** +//// * /*5*/ +//// */ +//// ` +//// ` +//// // /*6*/ +//// /* /*7*/ */ +//// /** +//// * /*8*/ +//// */ + +for (let i = 0; i < 9; ++i) { + goTo.marker(i.toString()); + verify.not.isInCommentAtPosition(); +} \ No newline at end of file diff --git a/tests/cases/fourslash/isInMultiLineCommentOnlyTrivia.ts b/tests/cases/fourslash/isInMultiLineCommentOnlyTrivia.ts new file mode 100644 index 00000000000..41c9bbadfd8 --- /dev/null +++ b/tests/cases/fourslash/isInMultiLineCommentOnlyTrivia.ts @@ -0,0 +1,37 @@ +/// + +//// /* x */ +//// /** +//// * @param this doesn't make sense here. +//// */ +//// // x + +const firstCommentStart = 0; +const firstCommentEnd = 7; +goTo.position(firstCommentStart); +verify.not.isInCommentAtPosition(); + +goTo.position(firstCommentStart + 1); +verify.isInCommentAtPosition(); +goTo.position(firstCommentEnd - 1); +verify.isInCommentAtPosition(); + +goTo.position(firstCommentEnd); +verify.not.isInCommentAtPosition(); + +const multilineJsDocStart = firstCommentEnd + 1; +const multilineJsDocEnd = multilineJsDocStart + 49; + +goTo.position(multilineJsDocStart); +verify.not.isInCommentAtPosition(); +goTo.position(multilineJsDocStart + 1); +verify.isInCommentAtPosition(); +goTo.position(multilineJsDocEnd - 1); +verify.isInCommentAtPosition(); +goTo.position(multilineJsDocEnd); +verify.not.isInCommentAtPosition(); + +const singleLineCommentStart = multilineJsDocEnd + 1; + +goTo.position(singleLineCommentStart + 1); +verify.isInCommentAtPosition(/*onlyMultiLineDiverges*/ true); \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoTypeError.ts b/tests/cases/fourslash/quickInfoTypeError.ts new file mode 100644 index 00000000000..7e0c9b20303 --- /dev/null +++ b/tests/cases/fourslash/quickInfoTypeError.ts @@ -0,0 +1,10 @@ +/// + +////foo({ +//// /**/f: function() {}, +//// f() {} +////}); + +// The symbol indicates that this is a funciton, but the type is `any`. +// Regression test that we don't crash (by trying to get signatures from `any`). +verify.quickInfoAt("", "(method) f");