diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 505714108e3..4b1ab123051 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10891,7 +10891,11 @@ namespace ts { } function getAliasSymbolForTypeNode(node: TypeNode) { - return isTypeAlias(node.parent) ? getSymbolOfNode(node.parent) : undefined; + let host = node.parent; + while (isParenthesizedTypeNode(host)) { + host = host.parent; + } + return isTypeAlias(host) ? getSymbolOfNode(host) : undefined; } function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0bd4693875e..cdfacf6a9ee 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -5116,6 +5116,10 @@ "category": "Message", "code": 95088 }, + "Add 'await' to initializers": { + "category": "Message", + "code": 95089 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 414dc509d0f..49f1fd26bc3 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -90,14 +90,28 @@ namespace ts { return false; } - const nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); + let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); if (nextDirectorySeparator === -1) { // ignore "/user", "c:/users" or "c:/folderAtRoot" return false; } - if (dirPath.charCodeAt(0) !== CharacterCodes.slash && - dirPath.substr(rootLength, nextDirectorySeparator).search(/users/i) === -1) { + let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1); + const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash; + if (isNonDirectorySeparatorRoot && + dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths + pathPartForUserCheck.search(/[a-zA-z]\$\//) === 0) { // Dos style nextPart + nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1); + if (nextDirectorySeparator === -1) { + // ignore "//vda1cs4850/c$/folderAtRoot" + return false; + } + + pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1); + } + + if (isNonDirectorySeparatorRoot && + pathPartForUserCheck.search(/users\//i) !== 0) { // Paths like c:/folderAtRoot/subFolder are allowed return true; } @@ -105,7 +119,7 @@ namespace ts { for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; if (searchIndex === 0) { - // Folder isnt at expected minimun levels + // Folder isnt at expected minimum levels return false; } } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 6695d6ab86c..ad60d29866d 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -2183,7 +2183,7 @@ namespace ts { if (isIdentifierStart(ch, languageVersion)) { let char = ch; - while (pos < end && isIdentifierPart(char = codePointAt(text, pos), languageVersion)) pos += charSize(char); + while (pos < end && isIdentifierPart(char = codePointAt(text, pos), languageVersion) || text.charCodeAt(pos) === CharacterCodes.minus) pos += charSize(char); tokenValue = text.substring(tokenPos, pos); if (char === CharacterCodes.backslash) { tokenValue += scanIdentifierParts(); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 435582a9c2e..1a2fd5f4cc8 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -35,38 +35,16 @@ interface Array { length: number; [n: number]: T; }` executingFilePath?: string; currentDirectory?: string; newLine?: string; - useWindowsStylePaths?: boolean; + windowsStyleRoot?: string; environmentVariables?: Map; } export function createWatchedSystem(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost { - if (!params) { - params = {}; - } - const host = new TestServerHost(/*withSafelist*/ false, - params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false, - params.executingFilePath || getExecutingFilePathFromLibFile(), - params.currentDirectory || "/", - fileOrFolderList, - params.newLine, - params.useWindowsStylePaths, - params.environmentVariables); - return host; + return new TestServerHost(/*withSafelist*/ false, fileOrFolderList, params); } export function createServerHost(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost { - if (!params) { - params = {}; - } - const host = new TestServerHost(/*withSafelist*/ true, - params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false, - params.executingFilePath || getExecutingFilePathFromLibFile(), - params.currentDirectory || "/", - fileOrFolderList, - params.newLine, - params.useWindowsStylePaths, - params.environmentVariables); - return host; + return new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params); } export interface File { @@ -326,6 +304,16 @@ interface Array { length: number; [n: number]: T; }` } const timeIncrements = 1000; + export interface TestServerHostOptions { + useCaseSensitiveFileNames: boolean; + executingFilePath: string; + currentDirectory: string; + fileOrFolderorSymLinkList: readonly FileOrFolderOrSymLink[]; + newLine?: string; + useWindowsStylePaths?: boolean; + environmentVariables?: Map; + } + export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, ModuleResolutionHost { args: string[] = []; @@ -342,16 +330,31 @@ interface Array { length: number; [n: number]: T; }` readonly watchedDirectories = createMultiMap(); readonly watchedDirectoriesRecursive = createMultiMap(); readonly watchedFiles = createMultiMap(); + public readonly useCaseSensitiveFileNames: boolean; + public readonly newLine: string; + public readonly windowsStyleRoot?: string; + private readonly environmentVariables?: Map; private readonly executingFilePath: string; private readonly currentDirectory: string; private readonly customWatchFile: HostWatchFile | undefined; private readonly customRecursiveWatchDirectory: HostWatchDirectory | undefined; public require: ((initialPath: string, moduleName: string) => server.RequireResult) | undefined; - constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, executingFilePath: string, currentDirectory: string, fileOrFolderorSymLinkList: readonly FileOrFolderOrSymLink[], public readonly newLine = "\n", public readonly useWindowsStylePath?: boolean, private readonly environmentVariables?: Map) { - this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + constructor( + public withSafeList: boolean, + fileOrFolderorSymLinkList: readonly FileOrFolderOrSymLink[], + { + useCaseSensitiveFileNames, executingFilePath, currentDirectory, + newLine, windowsStyleRoot, environmentVariables + }: TestServerHostCreationParameters = {}) { + this.useCaseSensitiveFileNames = !!useCaseSensitiveFileNames; + this.newLine = newLine || "\n"; + this.windowsStyleRoot = windowsStyleRoot; + this.environmentVariables = environmentVariables; + currentDirectory = currentDirectory || "/"; + this.getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName); - this.executingFilePath = this.getHostSpecificPath(executingFilePath); + this.executingFilePath = this.getHostSpecificPath(executingFilePath || getExecutingFilePathFromLibFile()); this.currentDirectory = this.getHostSpecificPath(currentDirectory); this.reloadFS(fileOrFolderorSymLinkList); const tscWatchFile = this.environmentVariables && this.environmentVariables.get("TSC_WATCHFILE") as Tsc_WatchFile; @@ -418,8 +421,8 @@ interface Array { length: number; [n: number]: T; }` } getHostSpecificPath(s: string) { - if (this.useWindowsStylePath && s.startsWith(directorySeparator)) { - return "c:/" + s.substring(1); + if (this.windowsStyleRoot && s.startsWith(directorySeparator)) { + return this.windowsStyleRoot + s.substring(1); } return s; } @@ -433,7 +436,7 @@ interface Array { length: number; [n: number]: T; }` const mapNewLeaves = createMap(); const isNewFs = this.fs.size === 0; fileOrFolderOrSymLinkList = fileOrFolderOrSymLinkList.concat(this.withSafeList ? safeList : []); - const filesOrFoldersToLoad: readonly FileOrFolderOrSymLink[] = !this.useWindowsStylePath ? fileOrFolderOrSymLinkList : + const filesOrFoldersToLoad: readonly FileOrFolderOrSymLink[] = !this.windowsStyleRoot ? fileOrFolderOrSymLinkList : fileOrFolderOrSymLinkList.map(f => { const result = clone(f); result.path = this.getHostSpecificPath(f.path); diff --git a/src/services/codefixes/addMissingAwait.ts b/src/services/codefixes/addMissingAwait.ts index d6f5773d59a..233c91b153b 100644 --- a/src/services/codefixes/addMissingAwait.ts +++ b/src/services/codefixes/addMissingAwait.ts @@ -31,7 +31,7 @@ namespace ts.codefix { errorCodes, getCodeActions: context => { const { sourceFile, errorCode, span, cancellationToken, program } = context; - const expression = getAwaitableExpression(sourceFile, errorCode, span, cancellationToken, program); + const expression = getFixableErrorSpanExpression(sourceFile, errorCode, span, cancellationToken, program); if (!expression) { return; } @@ -45,32 +45,40 @@ namespace ts.codefix { getAllCodeActions: context => { const { sourceFile, program, cancellationToken } = context; const checker = context.program.getTypeChecker(); + const fixedDeclarations = createMap(); return codeFixAll(context, errorCodes, (t, diagnostic) => { - const expression = getAwaitableExpression(sourceFile, diagnostic.code, diagnostic, cancellationToken, program); + const expression = getFixableErrorSpanExpression(sourceFile, diagnostic.code, diagnostic, cancellationToken, program); if (!expression) { return; } const trackChanges: ContextualTrackChangesFunction = cb => (cb(t), []); - return getDeclarationSiteFix(context, expression, diagnostic.code, checker, trackChanges) - || getUseSiteFix(context, expression, diagnostic.code, checker, trackChanges); + return getDeclarationSiteFix(context, expression, diagnostic.code, checker, trackChanges, fixedDeclarations) + || getUseSiteFix(context, expression, diagnostic.code, checker, trackChanges, fixedDeclarations); }); }, }); - function getDeclarationSiteFix(context: CodeFixContext | CodeFixAllContext, expression: Expression, errorCode: number, checker: TypeChecker, trackChanges: ContextualTrackChangesFunction) { - const { sourceFile } = context; - const awaitableInitializer = findAwaitableInitializer(expression, sourceFile, checker); - if (awaitableInitializer) { - const initializerChanges = trackChanges(t => makeChange(t, errorCode, sourceFile, checker, awaitableInitializer)); + function getDeclarationSiteFix(context: CodeFixContext | CodeFixAllContext, expression: Expression, errorCode: number, checker: TypeChecker, trackChanges: ContextualTrackChangesFunction, fixedDeclarations?: Map) { + const { sourceFile, program, cancellationToken } = context; + const awaitableInitializers = findAwaitableInitializers(expression, sourceFile, cancellationToken, program, checker); + if (awaitableInitializers) { + const initializerChanges = trackChanges(t => { + forEach(awaitableInitializers.initializers, ({ expression }) => makeChange(t, errorCode, sourceFile, checker, expression, fixedDeclarations)); + if (fixedDeclarations && awaitableInitializers.needsSecondPassForFixAll) { + makeChange(t, errorCode, sourceFile, checker, expression, fixedDeclarations); + } + }); return createCodeFixActionNoFixId( "addMissingAwaitToInitializer", initializerChanges, - [Diagnostics.Add_await_to_initializer_for_0, expression.getText(sourceFile)]); + awaitableInitializers.initializers.length === 1 + ? [Diagnostics.Add_await_to_initializer_for_0, awaitableInitializers.initializers[0].declarationSymbol.name] + : Diagnostics.Add_await_to_initializers); } } - function getUseSiteFix(context: CodeFixContext | CodeFixAllContext, expression: Expression, errorCode: number, checker: TypeChecker, trackChanges: ContextualTrackChangesFunction) { - const changes = trackChanges(t => makeChange(t, errorCode, context.sourceFile, checker, expression)); + function getUseSiteFix(context: CodeFixContext | CodeFixAllContext, expression: Expression, errorCode: number, checker: TypeChecker, trackChanges: ContextualTrackChangesFunction, fixedDeclarations?: Map) { + const changes = trackChanges(t => makeChange(t, errorCode, context.sourceFile, checker, expression, fixedDeclarations)); return createCodeFixAction(fixId, changes, Diagnostics.Add_await, fixId, Diagnostics.Fix_all_expressions_possibly_missing_await); } @@ -84,7 +92,7 @@ namespace ts.codefix { some(relatedInformation, related => related.code === Diagnostics.Did_you_forget_to_use_await.code)); } - function getAwaitableExpression(sourceFile: SourceFile, errorCode: number, span: TextSpan, cancellationToken: CancellationToken, program: Program): Expression | undefined { + function getFixableErrorSpanExpression(sourceFile: SourceFile, errorCode: number, span: TextSpan, cancellationToken: CancellationToken, program: Program): Expression | undefined { const token = getTokenAtPosition(sourceFile, span.start); // Checker has already done work to determine that await might be possible, and has attached // related info to the node, so start by finding the expression that exactly matches up @@ -101,38 +109,117 @@ namespace ts.codefix { && isInsideAwaitableBody(expression) ? expression : undefined; } - function findAwaitableInitializer(expression: Node, sourceFile: SourceFile, checker: TypeChecker): Expression | undefined { - if (!isIdentifier(expression)) { + interface AwaitableInitializer { + expression: Expression; + declarationSymbol: Symbol; + } + + interface AwaitableInitializers { + initializers: readonly AwaitableInitializer[]; + needsSecondPassForFixAll: boolean; + } + + function findAwaitableInitializers( + expression: Node, + sourceFile: SourceFile, + cancellationToken: CancellationToken, + program: Program, + checker: TypeChecker, + ): AwaitableInitializers | undefined { + const identifiers = getIdentifiersFromErrorSpanExpression(expression, checker); + if (!identifiers) { return; } - const symbol = checker.getSymbolAtLocation(expression); - if (!symbol) { - return; + let isCompleteFix = identifiers.isCompleteFix; + let initializers: AwaitableInitializer[] | undefined; + for (const identifier of identifiers.identifiers) { + const symbol = checker.getSymbolAtLocation(identifier); + if (!symbol) { + continue; + } + + const declaration = tryCast(symbol.valueDeclaration, isVariableDeclaration); + const variableName = declaration && tryCast(declaration.name, isIdentifier); + const variableStatement = getAncestor(declaration, SyntaxKind.VariableStatement); + if (!declaration || !variableStatement || + declaration.type || + !declaration.initializer || + variableStatement.getSourceFile() !== sourceFile || + hasModifier(variableStatement, ModifierFlags.Export) || + !variableName || + !isInsideAwaitableBody(declaration.initializer)) { + isCompleteFix = false; + continue; + } + + const diagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken); + const isUsedElsewhere = FindAllReferences.Core.eachSymbolReferenceInFile(variableName, checker, sourceFile, reference => { + return identifier !== reference && !symbolReferenceIsAlsoMissingAwait(reference, diagnostics, sourceFile, checker); + }); + + if (isUsedElsewhere) { + isCompleteFix = false; + continue; + } + + (initializers || (initializers = [])).push({ + expression: declaration.initializer, + declarationSymbol: symbol, + }); } + return initializers && { + initializers, + needsSecondPassForFixAll: !isCompleteFix, + }; + } - const declaration = tryCast(symbol.valueDeclaration, isVariableDeclaration); - const variableName = tryCast(declaration && declaration.name, isIdentifier); - const variableStatement = getAncestor(declaration, SyntaxKind.VariableStatement); - if (!declaration || !variableStatement || - declaration.type || - !declaration.initializer || - variableStatement.getSourceFile() !== sourceFile || - hasModifier(variableStatement, ModifierFlags.Export) || - !variableName || - !isInsideAwaitableBody(declaration.initializer)) { - return; + interface Identifiers { + identifiers: readonly Identifier[]; + isCompleteFix: boolean; + } + + function getIdentifiersFromErrorSpanExpression(expression: Node, checker: TypeChecker): Identifiers | undefined { + if (isPropertyAccessExpression(expression.parent) && isIdentifier(expression.parent.expression)) { + return { identifiers: [expression.parent.expression], isCompleteFix: true }; } - - const isUsedElsewhere = FindAllReferences.Core.eachSymbolReferenceInFile(variableName, checker, sourceFile, identifier => { - return identifier !== expression; - }); - - if (isUsedElsewhere) { - return; + if (isIdentifier(expression)) { + return { identifiers: [expression], isCompleteFix: true }; } + if (isBinaryExpression(expression)) { + let sides: Identifier[] | undefined; + let isCompleteFix = true; + for (const side of [expression.left, expression.right]) { + const type = checker.getTypeAtLocation(side); + if (checker.getPromisedTypeOfPromise(type)) { + if (!isIdentifier(side)) { + isCompleteFix = false; + continue; + } + (sides || (sides = [])).push(side); + } + } + return sides && { identifiers: sides, isCompleteFix }; + } + } - return declaration.initializer; + function symbolReferenceIsAlsoMissingAwait(reference: Identifier, diagnostics: readonly Diagnostic[], sourceFile: SourceFile, checker: TypeChecker) { + const errorNode = isPropertyAccessExpression(reference.parent) ? reference.parent.name : + isBinaryExpression(reference.parent) ? reference.parent : + reference; + const diagnostic = find(diagnostics, diagnostic => + diagnostic.start === errorNode.getStart(sourceFile) && + diagnostic.start + diagnostic.length! === errorNode.getEnd()); + + return diagnostic && contains(errorCodes, diagnostic.code) || + // A Promise is usually not correct in a binary expression (it’s not valid + // in an arithmetic expression and an equality comparison seems unusual), + // but if the other side of the binary expression has an error, the side + // is typed `any` which will squash the error that would identify this + // Promise as an invalid operand. So if the whole binary expression is + // typed `any` as a result, there is a strong likelihood that this Promise + // is accidentally missing `await`. + checker.getTypeAtLocation(errorNode).flags & TypeFlags.Any; } function isInsideAwaitableBody(node: Node) { @@ -145,26 +232,48 @@ namespace ts.codefix { ancestor.parent.kind === SyntaxKind.MethodDeclaration)); } - function makeChange(changeTracker: textChanges.ChangeTracker, errorCode: number, sourceFile: SourceFile, checker: TypeChecker, insertionSite: Expression) { + function makeChange(changeTracker: textChanges.ChangeTracker, errorCode: number, sourceFile: SourceFile, checker: TypeChecker, insertionSite: Expression, fixedDeclarations?: Map) { if (isBinaryExpression(insertionSite)) { - const { left, right } = insertionSite; - const leftType = checker.getTypeAtLocation(left); - const rightType = checker.getTypeAtLocation(right); - const newLeft = checker.getPromisedTypeOfPromise(leftType) ? createAwait(left) : left; - const newRight = checker.getPromisedTypeOfPromise(rightType) ? createAwait(right) : right; - changeTracker.replaceNode(sourceFile, left, newLeft); - changeTracker.replaceNode(sourceFile, right, newRight); + for (const side of [insertionSite.left, insertionSite.right]) { + if (fixedDeclarations && isIdentifier(side)) { + const symbol = checker.getSymbolAtLocation(side); + if (symbol && fixedDeclarations.has(getSymbolId(symbol).toString())) { + continue; + } + } + const type = checker.getTypeAtLocation(side); + const newNode = checker.getPromisedTypeOfPromise(type) ? createAwait(side) : side; + changeTracker.replaceNode(sourceFile, side, newNode); + } } else if (errorCode === propertyAccessCode && isPropertyAccessExpression(insertionSite.parent)) { + if (fixedDeclarations && isIdentifier(insertionSite.parent.expression)) { + const symbol = checker.getSymbolAtLocation(insertionSite.parent.expression); + if (symbol && fixedDeclarations.has(getSymbolId(symbol).toString())) { + return; + } + } changeTracker.replaceNode( sourceFile, insertionSite.parent.expression, createParen(createAwait(insertionSite.parent.expression))); } else if (contains(callableConstructableErrorCodes, errorCode) && isCallOrNewExpression(insertionSite.parent)) { + if (fixedDeclarations && isIdentifier(insertionSite)) { + const symbol = checker.getSymbolAtLocation(insertionSite); + if (symbol && fixedDeclarations.has(getSymbolId(symbol).toString())) { + return; + } + } changeTracker.replaceNode(sourceFile, insertionSite, createParen(createAwait(insertionSite))); } else { + if (fixedDeclarations && isVariableDeclaration(insertionSite.parent) && isIdentifier(insertionSite.parent.name)) { + const symbol = checker.getSymbolAtLocation(insertionSite.parent.name); + if (symbol && !addToSeen(fixedDeclarations, getSymbolId(symbol))) { + return; + } + } changeTracker.replaceNode(sourceFile, insertionSite, createAwait(insertionSite)); } } diff --git a/src/testRunner/parallel/host.ts b/src/testRunner/parallel/host.ts index dfd187d09f7..56fa17dd9e1 100644 --- a/src/testRunner/parallel/host.ts +++ b/src/testRunner/parallel/host.ts @@ -626,6 +626,7 @@ namespace Harness.Parallel.Host { const perfData = readSavedPerfData(configOption); context.describe = addSuite as Mocha.SuiteFunction; + context.it = addSuite as Mocha.TestFunction; function addSuite(title: string) { // Note, sub-suites are not indexed (we assume such granularity is not required) diff --git a/src/testRunner/parallel/worker.ts b/src/testRunner/parallel/worker.ts index ccb654d964c..a7c22ab6eb7 100644 --- a/src/testRunner/parallel/worker.ts +++ b/src/testRunner/parallel/worker.ts @@ -151,24 +151,42 @@ namespace Harness.Parallel.Worker { unitTestSuiteMap.set(suite.title, suite); } } + if (!unitTestTestMap && unitTestSuite.tests.length) { + unitTestTestMap = ts.createMap(); + for (const test of unitTestSuite.tests) { + unitTestTestMap.set(test.title, test); + } + } - if (!unitTestSuiteMap) { + if (!unitTestSuiteMap && !unitTestTestMap) { throw new Error(`Asked to run unit test ${task.file}, but no unit tests were discovered!`); } - const suite = unitTestSuiteMap.get(task.file); - if (!suite) { + let suite = unitTestSuiteMap.get(task.file); + const test = unitTestTestMap.get(task.file); + if (!suite && !test) { throw new Error(`Unit test with name "${task.file}" was asked to be run, but such a test does not exist!`); } const root = new Suite("", new Mocha.Context()); root.timeout(globalTimeout || 40_000); - root.addSuite(suite); - Object.setPrototypeOf(suite.ctx, root.ctx); + if (suite) { + root.addSuite(suite); + Object.setPrototypeOf(suite.ctx, root.ctx); + } + else if (test) { + const newSuite = new Suite("", new Mocha.Context()); + newSuite.addTest(test); + root.addSuite(newSuite); + Object.setPrototypeOf(newSuite.ctx, root.ctx); + Object.setPrototypeOf(test.ctx, root.ctx); + test.parent = newSuite; + suite = newSuite; + } - runSuite(task, suite, payload => { - suite.parent = unitTestSuite; - Object.setPrototypeOf(suite.ctx, unitTestSuite.ctx); + runSuite(task, suite!, payload => { + suite!.parent = unitTestSuite; + Object.setPrototypeOf(suite!.ctx, unitTestSuite.ctx); fn(payload); }); } @@ -284,6 +302,8 @@ namespace Harness.Parallel.Worker { // The root suite for all unit tests. let unitTestSuite: Suite; let unitTestSuiteMap: ts.Map; + // (Unit) Tests directly within the root suite + let unitTestTestMap: ts.Map; if (runUnitTests) { unitTestSuite = new Suite("", new Mocha.Context()); diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index 35d1dc374b5..7d92949b526 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1064,7 +1064,7 @@ namespace ts.projectSystem { content: "let x = 1;" }; - const host = createServerHost([file1, configFile], { useWindowsStylePaths: true }); + const host = createServerHost([file1, configFile], { windowsStyleRoot: "c:/" }); const projectService = createProjectService(host); projectService.openClientFile(file1.path); diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts index 5e776aa9908..63afebab2f6 100644 --- a/src/testRunner/unittests/tsserver/watchEnvironment.ts +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -99,7 +99,7 @@ namespace ts.projectSystem { content: "let y = 10;" }; const files = [configFile, file1, file2, libFile]; - const host = createServerHost(files, { useWindowsStylePaths: true }); + const host = createServerHost(files, { windowsStyleRoot: "c:/" }); const projectService = createProjectService(host); projectService.openClientFile(file1.path); const project = projectService.configuredProjects.get(configFile.path)!; @@ -211,4 +211,39 @@ namespace ts.projectSystem { } }); + describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watching files with network style paths", () => { + function verifyFilePathStyle(path: string) { + const windowsStyleRoot = path.substr(0, getRootLength(path)); + const host = createServerHost( + [libFile, { path, content: "const x = 10" }], + { windowsStyleRoot } + ); + const service = createProjectService(host); + service.openClientFile(path); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const libPath = `${windowsStyleRoot}${libFile.path.substring(1)}`; + checkProjectActualFiles(service.inferredProjects[0], [path, libPath]); + checkWatchedFiles(host, [libPath, `${getDirectoryPath(path)}/tsconfig.json`, `${getDirectoryPath(path)}/jsconfig.json`]); + } + + it("for file of style c:/myprojects/project/x.js", () => { + verifyFilePathStyle("c:/myprojects/project/x.js"); + }); + + it("for file of style //vda1cs4850/myprojects/project/x.js", () => { + verifyFilePathStyle("//vda1cs4850/myprojects/project/x.js"); + }); + + it("for file of style //vda1cs4850/c$/myprojects/project/x.js", () => { + verifyFilePathStyle("//vda1cs4850/c$/myprojects/project/x.js"); + }); + + it("for file of style c:/users/username/myprojects/project/x.js", () => { + verifyFilePathStyle("c:/users/username/myprojects/project/x.js"); + }); + + it("for file of style //vda1cs4850/c$/users/username/myprojects/project/x.js", () => { + verifyFilePathStyle("//vda1cs4850/c$/users/username/myprojects/project/x.js"); + }); + }); } diff --git a/tests/baselines/reference/destructuringInFunctionType.types b/tests/baselines/reference/destructuringInFunctionType.types index 2ef48fcbea2..c6c0f439ac5 100644 --- a/tests/baselines/reference/destructuringInFunctionType.types +++ b/tests/baselines/reference/destructuringInFunctionType.types @@ -18,7 +18,7 @@ type F1 = ([a, b, c]) => void; >c : any type T2 = ({ a }); ->T2 : { a: any; } +>T2 : T2 >a : any type F2 = ({ a }) => void; diff --git a/tests/baselines/reference/keyofIntersection.types b/tests/baselines/reference/keyofIntersection.types index b1c0fe8988c..ddef2cd4b1a 100644 --- a/tests/baselines/reference/keyofIntersection.types +++ b/tests/baselines/reference/keyofIntersection.types @@ -46,7 +46,7 @@ type Result3 = Example3<'x' | 'y'>; // "x" | "y" >Result3 : "x" | "y" type Example4 = (Record & Record); ->Example4 : Record & Record +>Example4 : Example4 type Result4 = keyof Example4<'x', 'y'>; // "x" | "y" >Result4 : "x" | "y" diff --git a/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.js b/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.js new file mode 100644 index 00000000000..6a779f7cdf9 --- /dev/null +++ b/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.js @@ -0,0 +1,49 @@ +//// [parenthesisDoesNotBlockAliasSymbolCreation.ts] +export type InvalidKeys = { [P in K]? : never }; +export type InvalidKeys2 = ( + { [P in K]? : never } +); + +export type A = ( + T & InvalidKeys<"a"> +); +export type A2 = ( + T & InvalidKeys2<"a"> +); + +export const a = null as A<{ x : number }>; +export const a2 = null as A2<{ x : number }>; +export const a3 = null as { x : number } & InvalidKeys<"a">; +export const a4 = null as { x : number } & InvalidKeys2<"a">; + + +//// [parenthesisDoesNotBlockAliasSymbolCreation.js] +"use strict"; +exports.__esModule = true; +exports.a = null; +exports.a2 = null; +exports.a3 = null; +exports.a4 = null; + + +//// [parenthesisDoesNotBlockAliasSymbolCreation.d.ts] +export declare type InvalidKeys = { + [P in K]?: never; +}; +export declare type InvalidKeys2 = ({ + [P in K]?: never; +}); +export declare type A = (T & InvalidKeys<"a">); +export declare type A2 = (T & InvalidKeys2<"a">); +export declare const a: A<{ + x: number; +}>; +export declare const a2: A2<{ + x: number; +}>; +export declare const a3: { + x: number; +} & InvalidKeys<"a">; +export declare const a4: { + x: number; +} & InvalidKeys2<"a">; diff --git a/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.symbols b/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.symbols new file mode 100644 index 00000000000..83c9bf4deed --- /dev/null +++ b/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.symbols @@ -0,0 +1,56 @@ +=== tests/cases/compiler/parenthesisDoesNotBlockAliasSymbolCreation.ts === +export type InvalidKeys = { [P in K]? : never }; +>InvalidKeys : Symbol(InvalidKeys, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 0)) +>K : Symbol(K, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 24)) +>P : Symbol(P, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 61)) +>K : Symbol(K, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 24)) + +export type InvalidKeys2 = ( +>InvalidKeys2 : Symbol(InvalidKeys2, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 80)) +>K : Symbol(K, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 1, 25)) + + { [P in K]? : never } +>P : Symbol(P, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 2, 7)) +>K : Symbol(K, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 1, 25)) + +); + +export type A = ( +>A : Symbol(A, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 3, 2)) +>T : Symbol(T, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 5, 14)) + + T & InvalidKeys<"a"> +>T : Symbol(T, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 5, 14)) +>InvalidKeys : Symbol(InvalidKeys, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 0)) + +); +export type A2 = ( +>A2 : Symbol(A2, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 7, 2)) +>T : Symbol(T, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 8, 15)) + + T & InvalidKeys2<"a"> +>T : Symbol(T, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 8, 15)) +>InvalidKeys2 : Symbol(InvalidKeys2, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 80)) + +); + +export const a = null as A<{ x : number }>; +>a : Symbol(a, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 12, 12)) +>A : Symbol(A, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 3, 2)) +>x : Symbol(x, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 12, 28)) + +export const a2 = null as A2<{ x : number }>; +>a2 : Symbol(a2, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 13, 12)) +>A2 : Symbol(A2, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 7, 2)) +>x : Symbol(x, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 13, 30)) + +export const a3 = null as { x : number } & InvalidKeys<"a">; +>a3 : Symbol(a3, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 14, 12)) +>x : Symbol(x, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 14, 27)) +>InvalidKeys : Symbol(InvalidKeys, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 0)) + +export const a4 = null as { x : number } & InvalidKeys2<"a">; +>a4 : Symbol(a4, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 15, 12)) +>x : Symbol(x, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 15, 27)) +>InvalidKeys2 : Symbol(InvalidKeys2, Decl(parenthesisDoesNotBlockAliasSymbolCreation.ts, 0, 80)) + diff --git a/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.types b/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.types new file mode 100644 index 00000000000..5bf53abed1c --- /dev/null +++ b/tests/baselines/reference/parenthesisDoesNotBlockAliasSymbolCreation.types @@ -0,0 +1,45 @@ +=== tests/cases/compiler/parenthesisDoesNotBlockAliasSymbolCreation.ts === +export type InvalidKeys = { [P in K]? : never }; +>InvalidKeys : InvalidKeys + +export type InvalidKeys2 = ( +>InvalidKeys2 : InvalidKeys2 + + { [P in K]? : never } +); + +export type A = ( +>A : A + + T & InvalidKeys<"a"> +); +export type A2 = ( +>A2 : A2 + + T & InvalidKeys2<"a"> +); + +export const a = null as A<{ x : number }>; +>a : A<{ x: number; }> +>null as A<{ x : number }> : A<{ x: number; }> +>null : null +>x : number + +export const a2 = null as A2<{ x : number }>; +>a2 : A2<{ x: number; }> +>null as A2<{ x : number }> : A2<{ x: number; }> +>null : null +>x : number + +export const a3 = null as { x : number } & InvalidKeys<"a">; +>a3 : { x: number; } & InvalidKeys<"a"> +>null as { x : number } & InvalidKeys<"a"> : { x: number; } & InvalidKeys<"a"> +>null : null +>x : number + +export const a4 = null as { x : number } & InvalidKeys2<"a">; +>a4 : { x: number; } & InvalidKeys2<"a"> +>null as { x : number } & InvalidKeys2<"a"> : { x: number; } & InvalidKeys2<"a"> +>null : null +>x : number + diff --git a/tests/baselines/reference/tsbuild/moduleSpecifiers/initial-build/resolves-correctly.js b/tests/baselines/reference/tsbuild/moduleSpecifiers/initial-build/resolves-correctly.js index a664688e748..2ba8fa80817 100644 --- a/tests/baselines/reference/tsbuild/moduleSpecifiers/initial-build/resolves-correctly.js +++ b/tests/baselines/reference/tsbuild/moduleSpecifiers/initial-build/resolves-correctly.js @@ -14,8 +14,8 @@ exports.__esModule = true; "program": { "fileInfos": { "../../../.ts/lib.es5.d.ts": { - "version": "146944634190", - "signature": "146944634190" + "version": "406734842058", + "signature": "406734842058" }, "../../../.ts/lib.es2015.d.ts": { "version": "57263133672", @@ -114,8 +114,8 @@ exports.__esModule = true; "program": { "fileInfos": { "../../../.ts/lib.es5.d.ts": { - "version": "146944634190", - "signature": "146944634190" + "version": "406734842058", + "signature": "406734842058" }, "../../../.ts/lib.es2015.d.ts": { "version": "57263133672", @@ -237,8 +237,8 @@ exports.getVar = getVar; "program": { "fileInfos": { "../../../.ts/lib.es5.d.ts": { - "version": "146944634190", - "signature": "146944634190" + "version": "406734842058", + "signature": "406734842058" }, "../../../.ts/lib.es2015.d.ts": { "version": "57263133672", diff --git a/tests/cases/compiler/parenthesisDoesNotBlockAliasSymbolCreation.ts b/tests/cases/compiler/parenthesisDoesNotBlockAliasSymbolCreation.ts new file mode 100644 index 00000000000..0f8e993bbd6 --- /dev/null +++ b/tests/cases/compiler/parenthesisDoesNotBlockAliasSymbolCreation.ts @@ -0,0 +1,18 @@ +// @declaration: true + +export type InvalidKeys = { [P in K]? : never }; +export type InvalidKeys2 = ( + { [P in K]? : never } +); + +export type A = ( + T & InvalidKeys<"a"> +); +export type A2 = ( + T & InvalidKeys2<"a"> +); + +export const a = null as A<{ x : number }>; +export const a2 = null as A2<{ x : number }>; +export const a3 = null as { x : number } & InvalidKeys<"a">; +export const a4 = null as { x : number } & InvalidKeys2<"a">; diff --git a/tests/cases/fourslash/codeFixAddMissingAwait_initializer.ts b/tests/cases/fourslash/codeFixAddMissingAwait_initializer1.ts similarity index 100% rename from tests/cases/fourslash/codeFixAddMissingAwait_initializer.ts rename to tests/cases/fourslash/codeFixAddMissingAwait_initializer1.ts diff --git a/tests/cases/fourslash/codeFixAddMissingAwait_initializer2.ts b/tests/cases/fourslash/codeFixAddMissingAwait_initializer2.ts new file mode 100644 index 00000000000..4f5d604d448 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingAwait_initializer2.ts @@ -0,0 +1,15 @@ +/// +////async function fn(a: Promise) { +//// const x = a; +//// x.toLowerCase(); +////} + +verify.codeFix({ + description: "Add 'await' to initializer for 'x'", + index: 0, + newFileContent: +`async function fn(a: Promise) { + const x = await a; + x.toLowerCase(); +}` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingAwait_initializer3.ts b/tests/cases/fourslash/codeFixAddMissingAwait_initializer3.ts new file mode 100644 index 00000000000..9fd6cad6420 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingAwait_initializer3.ts @@ -0,0 +1,31 @@ +/// +////async function fn(a: number, b: Promise) { +//// const x = b; +//// const y = b; +//// fn(x, b); +//// fn(y, b); +//// x.toFixed(); +//// y.then; +//// +//// b + b; +//// x + b; +//// x + x.toFixed(); +////} + +verify.codeFixAll({ + fixAllDescription: ts.Diagnostics.Fix_all_expressions_possibly_missing_await.message, + fixId: "addMissingAwait", + newFileContent: +`async function fn(a: number, b: Promise) { + const x = await b; + const y = b; + fn(x, b); + fn(await y, b); + x.toFixed(); + y.then; + + await b + await b; + x + await b; + x + x.toFixed(); +}` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingAwait_initializer4.ts b/tests/cases/fourslash/codeFixAddMissingAwait_initializer4.ts new file mode 100644 index 00000000000..3106eaeccb6 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingAwait_initializer4.ts @@ -0,0 +1,28 @@ +/// +////async function fn(a: string, b: Promise) { +//// const x = b; +//// const y = b; +//// x + y; +////} + +verify.codeFix({ + description: "Add 'await' to initializers", + index: 0, + newFileContent: +`async function fn(a: string, b: Promise) { + const x = await b; + const y = await b; + x + y; +}` +}); + +verify.codeFixAll({ + fixAllDescription: ts.Diagnostics.Fix_all_expressions_possibly_missing_await.message, + fixId: "addMissingAwait", + newFileContent: +`async function fn(a: string, b: Promise) { + const x = await b; + const y = await b; + x + y; +}` +}); diff --git a/tests/cases/fourslash/jsDocTagsWithHyphen.ts b/tests/cases/fourslash/jsDocTagsWithHyphen.ts new file mode 100644 index 00000000000..188c4298bcb --- /dev/null +++ b/tests/cases/fourslash/jsDocTagsWithHyphen.ts @@ -0,0 +1,24 @@ +/// +// @allowJs: true +// @Filename: dummy.js + +//// /** +//// * @typedef Product +//// * @property {string} title +//// * @property {boolean} h/*1*/igh-top some-comments +//// */ +//// +//// /** +//// * @type {Pro/*2*/duct} +//// */ +//// const product = { +//// /*3*/ +//// } +verify.quickInfoAt('1', '(property) high-top: boolean', 'some-comments'); + +verify.quickInfoAt('2', 'type Product = {\n title: string;\n high-top: boolean;\n}'); + +verify.completions({ + marker: ['3'], + includes: ['"high-top"'] +}); diff --git a/tests/cases/user/prettier/prettier b/tests/cases/user/prettier/prettier index 23146404850..1e471a00796 160000 --- a/tests/cases/user/prettier/prettier +++ b/tests/cases/user/prettier/prettier @@ -1 +1 @@ -Subproject commit 23146404850011972f695fb6bc2b8113c3cffbfc +Subproject commit 1e471a007968b7490563b91ed6909ae6046f3fe8