mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-29 09:29:11 -05:00
More scenarios in their own test
This commit is contained in:
@@ -58,7 +58,6 @@
|
||||
"unittests/reuseProgramStructure.ts",
|
||||
"unittests/semver.ts",
|
||||
"unittests/transform.ts",
|
||||
"unittests/transpile.ts",
|
||||
"unittests/tsbuild.ts",
|
||||
"unittests/tsbuildWatchMode.ts",
|
||||
"unittests/tscWatchMode.ts",
|
||||
@@ -89,25 +88,50 @@
|
||||
"unittests/services/patternMatcher.ts",
|
||||
"unittests/services/preProcessFile.ts",
|
||||
"unittests/services/textChanges.ts",
|
||||
"unittests/services/transpile.ts",
|
||||
"unittests/tscWatch/emit.ts",
|
||||
"unittests/tscWatch/resolutionCache.ts",
|
||||
"unittests/tscWatch/watchEnvironment.ts",
|
||||
"unittests/tscWatch/watchApi.ts",
|
||||
"unittests/tsserver/cachingFileSystemInformation.ts",
|
||||
"unittests/tsserver/cancellationToken.ts",
|
||||
"unittests/tsserver/compileOnSave.ts",
|
||||
"unittests/tsserver/completions.ts",
|
||||
"unittests/tsserver/configFileSearch.ts",
|
||||
"unittests/tsserver/declarationFileMaps.ts",
|
||||
"unittests/tsserver/documentRegistry.ts",
|
||||
"unittests/tsserver/duplicatePackages.ts",
|
||||
"unittests/tsserver/events/largeFileReferenced.ts",
|
||||
"unittests/tsserver/events/projectLoading.ts",
|
||||
"unittests/tsserver/events/projectUpdatedInBackground.ts",
|
||||
"unittests/tsserver/externalProjects.ts",
|
||||
"unittests/tsserver/forceConsistentCasingInFileNames.ts",
|
||||
"unittests/tsserver/formatSettings.ts",
|
||||
"unittests/tsserver/getEditsForFileRename.ts",
|
||||
"unittests/tsserver/importHelpers.ts",
|
||||
"unittests/tsserver/inferredProjects.ts",
|
||||
"unittests/tsserver/languageService.ts",
|
||||
"unittests/tsserver/maxNodeModuleJsDepth.ts",
|
||||
"unittests/tsserver/metadataInResponse.ts",
|
||||
"unittests/tsserver/navTo.ts",
|
||||
"unittests/tsserver/occurences.ts",
|
||||
"unittests/tsserver/openFile.ts",
|
||||
"unittests/tsserver/projectErrors.ts",
|
||||
"unittests/tsserver/projectReferences.ts",
|
||||
"unittests/tsserver/refactors.ts",
|
||||
"unittests/tsserver/reload.ts",
|
||||
"unittests/tsserver/rename.ts",
|
||||
"unittests/tsserver/resolutionCache.ts",
|
||||
"unittests/tsserver/session.ts",
|
||||
"unittests/tsserver/skipLibCheck.ts",
|
||||
"unittests/tsserver/symLinks.ts",
|
||||
"unittests/tsserver/syntaxOperations.ts",
|
||||
"unittests/tsserver/textStorage.ts",
|
||||
"unittests/tsserver/telemetry.ts",
|
||||
"unittests/tsserver/typeAquisition.ts",
|
||||
"unittests/tsserver/typeReferenceDirectives.ts",
|
||||
"unittests/tsserver/typingsInstaller.ts",
|
||||
"unittests/tsserver/untitledFiles.ts",
|
||||
"unittests/tsserver/versionCache.ts",
|
||||
"unittests/tsserver/watchEnvironment.ts"
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("commandLineParsing:: parseCommandLine", () => {
|
||||
describe("config:: commandLineParsing:: parseCommandLine", () => {
|
||||
|
||||
function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine) {
|
||||
const parsed = parseCommandLine(commandLine);
|
||||
@@ -367,7 +367,7 @@ namespace ts {
|
||||
});
|
||||
});
|
||||
|
||||
describe("commandLineParsing:: parseBuildOptions", () => {
|
||||
describe("config:: commandLineParsing:: parseBuildOptions", () => {
|
||||
function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) {
|
||||
const parsed = parseBuildCommand(commandLine);
|
||||
const parsedBuildOptions = JSON.stringify(parsed.buildOptions);
|
||||
|
||||
@@ -208,7 +208,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
describe("configurationExtension", () => {
|
||||
describe("config:: configurationExtension", () => {
|
||||
forEach<[string, string, fakes.ParseConfigHost], void>([
|
||||
["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost],
|
||||
["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("convertCompilerOptionsFromJson", () => {
|
||||
describe("config:: convertCompilerOptionsFromJson", () => {
|
||||
const formatDiagnosticHost: FormatDiagnosticsHost = {
|
||||
getCurrentDirectory: () => "/apath/",
|
||||
getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ts {
|
||||
interface ExpectedResult { typeAcquisition: TypeAcquisition; errors: Diagnostic[]; }
|
||||
describe("convertTypeAcquisitionFromJson", () => {
|
||||
describe("config:: convertTypeAcquisitionFromJson", () => {
|
||||
function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) {
|
||||
assertTypeAcquisitionWithJson(json, configFileName, expectedResult);
|
||||
assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("initTSConfig", () => {
|
||||
describe("config:: initTSConfig", () => {
|
||||
function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) {
|
||||
describe(name, () => {
|
||||
const commandLine = parseCommandLine(commandLinesArgs);
|
||||
@@ -30,4 +30,4 @@ namespace ts {
|
||||
|
||||
initTSConfigCorrectly("Initialized TSConfig with advanced options", ["--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace ts {
|
||||
return createFileDiagnostic(file, start, length, diagnosticMessage, arg0);
|
||||
}
|
||||
|
||||
describe("matchFiles", () => {
|
||||
describe("config:: matchFiles", () => {
|
||||
it("with defaults", () => {
|
||||
const json = {};
|
||||
const expected: ParsedCommandLine = {
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace ts {
|
||||
checkResult(prog, host);
|
||||
}
|
||||
|
||||
describe("project-references meta check", () => {
|
||||
describe("config:: project-references meta check", () => {
|
||||
it("default setup was created correctly", () => {
|
||||
const spec: TestSpecification = {
|
||||
"/primary": {
|
||||
@@ -118,7 +118,7 @@ namespace ts {
|
||||
/**
|
||||
* Validate that we enforce the basic settings constraints for referenced projects
|
||||
*/
|
||||
describe("project-references constraint checking for settings", () => {
|
||||
describe("config:: project-references constraint checking for settings", () => {
|
||||
it("errors when declaration = false", () => {
|
||||
const spec: TestSpecification = {
|
||||
"/primary": {
|
||||
@@ -248,7 +248,7 @@ namespace ts {
|
||||
/**
|
||||
* Path mapping behavior
|
||||
*/
|
||||
describe("project-references path mapping", () => {
|
||||
describe("config:: project-references path mapping", () => {
|
||||
it("redirects to the output .d.ts file", () => {
|
||||
const spec: TestSpecification = {
|
||||
"/alpha": {
|
||||
@@ -268,7 +268,7 @@ namespace ts {
|
||||
});
|
||||
});
|
||||
|
||||
describe("project-references nice-behavior", () => {
|
||||
describe("config:: project-references nice-behavior", () => {
|
||||
it("issues a nice error when the input file is missing", () => {
|
||||
const spec: TestSpecification = {
|
||||
"/alpha": {
|
||||
@@ -289,7 +289,7 @@ namespace ts {
|
||||
/**
|
||||
* 'composite' behavior
|
||||
*/
|
||||
describe("project-references behavior changes under composite: true", () => {
|
||||
describe("config:: project-references behavior changes under composite: true", () => {
|
||||
it("doesn't infer the rootDir from source paths", () => {
|
||||
const spec: TestSpecification = {
|
||||
"/alpha": {
|
||||
@@ -308,7 +308,7 @@ namespace ts {
|
||||
});
|
||||
});
|
||||
|
||||
describe("project-references errors when a file in a composite project occurs outside the root", () => {
|
||||
describe("config:: project-references errors when a file in a composite project occurs outside the root", () => {
|
||||
it("Errors when a file is outside the rootdir", () => {
|
||||
const spec: TestSpecification = {
|
||||
"/alpha": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("showConfig", () => {
|
||||
describe("config:: showConfig", () => {
|
||||
function showTSConfigCorrectly(name: string, commandLinesArgs: string[], configJson?: object) {
|
||||
describe(name, () => {
|
||||
const outputFileName = `showConfig/${name.replace(/[^a-z0-9\-./ ]/ig, "")}/tsconfig.json`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("tsconfigParsing:: parseConfigFileTextToJson", () => {
|
||||
describe("config:: tsconfigParsing:: parseConfigFileTextToJson", () => {
|
||||
function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) {
|
||||
const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
|
||||
assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
describe("asyncArrowEvaluation", () => {
|
||||
describe("evaluation:: asyncArrowEvaluation", () => {
|
||||
// https://github.com/Microsoft/TypeScript/issues/24722
|
||||
it("this capture (es5)", async () => {
|
||||
const result = evaluator.evaluateTypeScript(`
|
||||
@@ -15,4 +15,4 @@ describe("asyncArrowEvaluation", () => {
|
||||
await result.main();
|
||||
assert.instanceOf(result.output[0].a(), result.A);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
describe("asyncGeneratorEvaluation", () => {
|
||||
describe("evaluation:: asyncGeneratorEvaluation", () => {
|
||||
it("return (es5)", async () => {
|
||||
const result = evaluator.evaluateTypeScript(`
|
||||
async function * g() {
|
||||
@@ -27,4 +27,4 @@ describe("asyncGeneratorEvaluation", () => {
|
||||
{ value: 0, done: true }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
describe("forAwaitOfEvaluation", () => {
|
||||
describe("evaluation:: forAwaitOfEvaluation", () => {
|
||||
it("sync (es5)", async () => {
|
||||
const result = evaluator.evaluateTypeScript(`
|
||||
let i = 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("cancellableLanguageServiceOperations", () => {
|
||||
describe("services:: cancellableLanguageServiceOperations", () => {
|
||||
const file = `
|
||||
function foo(): void;
|
||||
function foo<T>(x: T): T;
|
||||
|
||||
@@ -6,7 +6,7 @@ interface ClassificationEntry {
|
||||
position?: number;
|
||||
}
|
||||
|
||||
describe("Colorization", () => {
|
||||
describe("services:: Colorization", () => {
|
||||
// Use the shim adapter to ensure test coverage of the shim layer for the classifier
|
||||
const languageServiceAdapter = new Harness.LanguageService.ShimLanguageServiceAdapter(/*preprocessToResolve*/ false);
|
||||
const classifier = languageServiceAdapter.getClassifier();
|
||||
|
||||
@@ -343,7 +343,7 @@ interface Array<T> {}`
|
||||
}
|
||||
}
|
||||
|
||||
describe("convertToAsyncFunctions", () => {
|
||||
describe("services:: convertToAsyncFunctions", () => {
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_basic", `
|
||||
function [#|f|](): Promise<void>{
|
||||
return fetch('https://typescriptlang.org').then(result => { console.log(result) });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
describe("DocumentRegistry", () => {
|
||||
describe("services:: DocumentRegistry", () => {
|
||||
it("documents are shared between projects", () => {
|
||||
const documentRegistry = ts.createDocumentRegistry();
|
||||
const defaultCompilerOptions = ts.getDefaultCompilerOptions();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("extractConstants", () => {
|
||||
describe("services:: extract:: extractConstants", () => {
|
||||
testExtractConstant("extractConstant_TopLevel",
|
||||
`let x = [#|1|];`);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("extractFunctions", () => {
|
||||
describe("services:: extract:: extractFunctions", () => {
|
||||
testExtractFunction("extractFunction1",
|
||||
`namespace A {
|
||||
let x = 1;
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
describe("extractRanges", () => {
|
||||
describe("services:: extract:: extractRanges", () => {
|
||||
it("get extract range from selection", () => {
|
||||
testExtractRange(`
|
||||
[#|
|
||||
@@ -418,4 +418,4 @@ switch (x) {
|
||||
|
||||
testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("Symbol Walker", () => {
|
||||
describe("services:: extract:: Symbol Walker", () => {
|
||||
function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) {
|
||||
it(description, () => {
|
||||
const result = Harness.Compiler.compileFiles([{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("hostNewLineSupport", () => {
|
||||
describe("services:: hostNewLineSupport", () => {
|
||||
function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) {
|
||||
function snapFor(path: string): IScriptSnapshot | undefined {
|
||||
if (path === "lib.d.ts") {
|
||||
@@ -46,4 +46,4 @@ namespace ts {
|
||||
`);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("languageService", () => {
|
||||
describe("services:: languageService", () => {
|
||||
const files: {[index: string]: string} = {
|
||||
"foo.ts": `import Vue from "./vue";
|
||||
import Component from "./vue-class-component";
|
||||
@@ -43,4 +43,4 @@ export function Component(x: Config): any;`
|
||||
expect(definitions).to.exist; // tslint:disable-line no-unused-expression
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("Organize imports", () => {
|
||||
describe("services:: Organize imports", () => {
|
||||
describe("Sort imports", () => {
|
||||
it("Sort - non-relative vs non-relative", () => {
|
||||
assertSortsBefore(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
describe("PatternMatcher", () => {
|
||||
describe("services:: PatternMatcher", () => {
|
||||
describe("BreakIntoCharacterSpans", () => {
|
||||
it("EmptyIdentifier", () => {
|
||||
verifyBreakIntoCharacterSpans("");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
describe("PreProcessFile:", () => {
|
||||
describe("services:: PreProcessFile:", () => {
|
||||
function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void {
|
||||
const resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// tslint:disable trim-trailing-whitespace
|
||||
|
||||
namespace ts {
|
||||
describe("textChanges", () => {
|
||||
describe("services:: textChanges", () => {
|
||||
function findChild(name: string, n: Node) {
|
||||
return find(n)!;
|
||||
|
||||
@@ -753,4 +753,4 @@ let x = foo
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts {
|
||||
describe("Transpile", () => {
|
||||
describe("services:: Transpile", () => {
|
||||
|
||||
interface TranspileTestSettings {
|
||||
options?: TranspileOptions;
|
||||
@@ -38,7 +38,7 @@ namespace ts.tscWatch {
|
||||
checkOutputDoesNotContain(host, expectedNonAffectedFiles);
|
||||
}
|
||||
|
||||
describe("tsc-watch emit with outFile or out setting", () => {
|
||||
describe("tsc-watch:: emit with outFile or out setting", () => {
|
||||
function createWatchForOut(out?: string, outFile?: string) {
|
||||
const host = createWatchedSystem([]);
|
||||
const config: FileOrFolderEmit = {
|
||||
@@ -161,7 +161,7 @@ namespace ts.tscWatch {
|
||||
});
|
||||
});
|
||||
|
||||
describe("tsc-watch emit for configured projects", () => {
|
||||
describe("tsc-watch:: emit for configured projects", () => {
|
||||
const file1Consumer1Path = "/a/b/file1Consumer1.ts";
|
||||
const moduleFile1Path = "/a/b/moduleFile1.ts";
|
||||
const configFilePath = "/a/b/tsconfig.json";
|
||||
@@ -495,7 +495,7 @@ namespace ts.tscWatch {
|
||||
});
|
||||
});
|
||||
|
||||
describe("tsc-watch emit file content", () => {
|
||||
describe("tsc-watch:: emit file content", () => {
|
||||
interface EmittedFile extends File {
|
||||
shouldBeWritten: boolean;
|
||||
}
|
||||
@@ -676,7 +676,7 @@ namespace ts.tscWatch {
|
||||
});
|
||||
});
|
||||
|
||||
describe("tsc-watch with when module emit is specified as node", () => {
|
||||
describe("tsc-watch:: emit with when module emit is specified as node", () => {
|
||||
it("when instead of filechanged recursive directory watcher is invoked", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/rootFolder/project/tsconfig.json",
|
||||
|
||||
@@ -12,6 +12,15 @@ namespace ts.tscWatch {
|
||||
export import checkOutputContains = TestFSWithWatch.checkOutputContains;
|
||||
export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
|
||||
|
||||
export const commonFile1: File = {
|
||||
path: "/a/b/commonFile1.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
export const commonFile2: File = {
|
||||
path: "/a/b/commonFile2.ts",
|
||||
content: "let y = 1"
|
||||
};
|
||||
|
||||
export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray<string>) {
|
||||
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts.tscWatch {
|
||||
describe("resolutionCache:: tsc-watch module resolution caching", () => {
|
||||
describe("tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => {
|
||||
it("works", () => {
|
||||
const root = {
|
||||
path: "/a/d/f0.ts",
|
||||
@@ -404,7 +404,7 @@ declare module "fs" {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolutionCache:: tsc-watch with modules linked to sibling folder", () => {
|
||||
describe("tsc-watch:: resolutionCache:: tsc-watch with modules linked to sibling folder", () => {
|
||||
const projectRoot = "/user/username/projects/project";
|
||||
const mainPackageRoot = `${projectRoot}/main`;
|
||||
const linkedPackageRoot = `${projectRoot}/linked-package`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts.tscWatch {
|
||||
describe("watchAPI:: tsc-watch with custom module resolution", () => {
|
||||
describe("tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => {
|
||||
const projectRoot = "/user/username/projects/project";
|
||||
const configFileJson: any = {
|
||||
compilerOptions: { module: "commonjs", resolveJsonModule: true },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ts.tscWatch {
|
||||
import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
|
||||
describe("watchEnvironment:: tsc-watch with different polling/non polling options", () => {
|
||||
describe("tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => {
|
||||
it("watchFile using dynamic priority polling", () => {
|
||||
const projectFolder = "/a/username/project";
|
||||
const file1: File = {
|
||||
|
||||
@@ -25,15 +25,6 @@ namespace ts.tscWatch {
|
||||
}
|
||||
|
||||
describe("tsc-watch program updates", () => {
|
||||
const commonFile1: File = {
|
||||
path: "/a/b/commonFile1.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const commonFile2: File = {
|
||||
path: "/a/b/commonFile2.ts",
|
||||
content: "let y = 1"
|
||||
};
|
||||
|
||||
it("create watch without config file", () => {
|
||||
const appFile: File = {
|
||||
path: "/a/b/c/app.ts",
|
||||
|
||||
271
src/testRunner/unittests/tsserver/cancellationToken.ts
Normal file
271
src/testRunner/unittests/tsserver/cancellationToken.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: cancellationToken", () => {
|
||||
// Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test
|
||||
let oldPrepare: AnyFunction;
|
||||
before(() => {
|
||||
oldPrepare = (Error as any).prepareStackTrace;
|
||||
delete (Error as any).prepareStackTrace;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
(Error as any).prepareStackTrace = oldPrepare;
|
||||
});
|
||||
|
||||
it("is attached to request", () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let xyz = 1;"
|
||||
};
|
||||
const host = createServerHost([f1]);
|
||||
let expectedRequestId: number;
|
||||
const cancellationToken: server.ServerCancellationToken = {
|
||||
isCancellationRequested: () => false,
|
||||
setRequest: requestId => {
|
||||
if (expectedRequestId === undefined) {
|
||||
assert.isTrue(false, "unexpected call");
|
||||
}
|
||||
assert.equal(requestId, expectedRequestId);
|
||||
},
|
||||
resetRequest: noop
|
||||
};
|
||||
|
||||
const session = createSession(host, { cancellationToken });
|
||||
|
||||
expectedRequestId = session.getNextSeq();
|
||||
session.executeCommandSeq(<server.protocol.OpenRequest>{
|
||||
command: "open",
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
|
||||
expectedRequestId = session.getNextSeq();
|
||||
session.executeCommandSeq(<server.protocol.GeterrRequest>{
|
||||
command: "geterr",
|
||||
arguments: { files: [f1.path] }
|
||||
});
|
||||
|
||||
expectedRequestId = session.getNextSeq();
|
||||
session.executeCommandSeq(<server.protocol.OccurrencesRequest>{
|
||||
command: "occurrences",
|
||||
arguments: { file: f1.path, line: 1, offset: 6 }
|
||||
});
|
||||
|
||||
expectedRequestId = 2;
|
||||
host.runQueuedImmediateCallbacks();
|
||||
expectedRequestId = 2;
|
||||
host.runQueuedImmediateCallbacks();
|
||||
});
|
||||
|
||||
it("Geterr is cancellable", () => {
|
||||
const f1 = {
|
||||
path: "/a/app.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const config = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {}
|
||||
})
|
||||
};
|
||||
|
||||
const cancellationToken = new TestServerCancellationToken();
|
||||
const host = createServerHost([f1, config]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
eventHandler: noop,
|
||||
cancellationToken
|
||||
});
|
||||
{
|
||||
session.executeCommandSeq(<protocol.OpenRequest>{
|
||||
command: "open",
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
// send geterr for missing file
|
||||
session.executeCommandSeq(<protocol.GeterrRequest>{
|
||||
command: "geterr",
|
||||
arguments: { files: ["/a/missing"] }
|
||||
});
|
||||
// no files - expect 'completed' event
|
||||
assert.equal(host.getOutput().length, 1, "expect 1 message");
|
||||
verifyRequestCompleted(session.getSeq(), 0);
|
||||
}
|
||||
{
|
||||
const getErrId = session.getNextSeq();
|
||||
// send geterr for a valid file
|
||||
session.executeCommandSeq(<protocol.GeterrRequest>{
|
||||
command: "geterr",
|
||||
arguments: { files: [f1.path] }
|
||||
});
|
||||
|
||||
assert.equal(host.getOutput().length, 0, "expect 0 messages");
|
||||
|
||||
// run new request
|
||||
session.executeCommandSeq(<protocol.ProjectInfoRequest>{
|
||||
command: "projectInfo",
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
session.clearMessages();
|
||||
|
||||
// cancel previously issued Geterr
|
||||
cancellationToken.setRequestToCancel(getErrId);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
assert.equal(host.getOutput().length, 1, "expect 1 message");
|
||||
verifyRequestCompleted(getErrId, 0);
|
||||
|
||||
cancellationToken.resetToken();
|
||||
}
|
||||
{
|
||||
const getErrId = session.getNextSeq();
|
||||
session.executeCommandSeq(<protocol.GeterrRequest>{
|
||||
command: "geterr",
|
||||
arguments: { files: [f1.path] }
|
||||
});
|
||||
assert.equal(host.getOutput().length, 0, "expect 0 messages");
|
||||
|
||||
// run first step
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
assert.equal(host.getOutput().length, 1, "expect 1 message");
|
||||
const e1 = <protocol.Event>getMessage(0);
|
||||
assert.equal(e1.event, "syntaxDiag");
|
||||
session.clearMessages();
|
||||
|
||||
cancellationToken.setRequestToCancel(getErrId);
|
||||
host.runQueuedImmediateCallbacks();
|
||||
assert.equal(host.getOutput().length, 1, "expect 1 message");
|
||||
verifyRequestCompleted(getErrId, 0);
|
||||
|
||||
cancellationToken.resetToken();
|
||||
}
|
||||
{
|
||||
const getErrId = session.getNextSeq();
|
||||
session.executeCommandSeq(<protocol.GeterrRequest>{
|
||||
command: "geterr",
|
||||
arguments: { files: [f1.path] }
|
||||
});
|
||||
assert.equal(host.getOutput().length, 0, "expect 0 messages");
|
||||
|
||||
// run first step
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
assert.equal(host.getOutput().length, 1, "expect 1 message");
|
||||
const e1 = <protocol.Event>getMessage(0);
|
||||
assert.equal(e1.event, "syntaxDiag");
|
||||
session.clearMessages();
|
||||
|
||||
// the semanticDiag message
|
||||
host.runQueuedImmediateCallbacks();
|
||||
assert.equal(host.getOutput().length, 1);
|
||||
const e2 = <protocol.Event>getMessage(0);
|
||||
assert.equal(e2.event, "semanticDiag");
|
||||
session.clearMessages();
|
||||
|
||||
host.runQueuedImmediateCallbacks(1);
|
||||
assert.equal(host.getOutput().length, 2);
|
||||
const e3 = <protocol.Event>getMessage(0);
|
||||
assert.equal(e3.event, "suggestionDiag");
|
||||
verifyRequestCompleted(getErrId, 1);
|
||||
|
||||
cancellationToken.resetToken();
|
||||
}
|
||||
{
|
||||
const getErr1 = session.getNextSeq();
|
||||
session.executeCommandSeq(<protocol.GeterrRequest>{
|
||||
command: "geterr",
|
||||
arguments: { files: [f1.path] }
|
||||
});
|
||||
assert.equal(host.getOutput().length, 0, "expect 0 messages");
|
||||
// run first step
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
assert.equal(host.getOutput().length, 1, "expect 1 message");
|
||||
const e1 = <protocol.Event>getMessage(0);
|
||||
assert.equal(e1.event, "syntaxDiag");
|
||||
session.clearMessages();
|
||||
|
||||
session.executeCommandSeq(<protocol.GeterrRequest>{
|
||||
command: "geterr",
|
||||
arguments: { files: [f1.path] }
|
||||
});
|
||||
// make sure that getErr1 is completed
|
||||
verifyRequestCompleted(getErr1, 0);
|
||||
}
|
||||
|
||||
function verifyRequestCompleted(expectedSeq: number, n: number) {
|
||||
const event = <protocol.RequestCompletedEvent>getMessage(n);
|
||||
assert.equal(event.event, "requestCompleted");
|
||||
assert.equal(event.body.request_seq, expectedSeq, "expectedSeq");
|
||||
session.clearMessages();
|
||||
}
|
||||
|
||||
function getMessage(n: number) {
|
||||
return JSON.parse(server.extractMessage(host.getOutput()[n]));
|
||||
}
|
||||
});
|
||||
|
||||
it("Lower priority tasks are cancellable", () => {
|
||||
const f1 = {
|
||||
path: "/a/app.ts",
|
||||
content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";`
|
||||
};
|
||||
const config = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {}
|
||||
})
|
||||
};
|
||||
const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3);
|
||||
const host = createServerHost([f1, config]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true,
|
||||
eventHandler: noop,
|
||||
cancellationToken,
|
||||
throttleWaitMilliseconds: 0
|
||||
});
|
||||
{
|
||||
session.executeCommandSeq(<protocol.OpenRequest>{
|
||||
command: "open",
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
|
||||
// send navbar request (normal priority)
|
||||
session.executeCommandSeq(<protocol.NavBarRequest>{
|
||||
command: "navbar",
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
|
||||
// ensure the nav bar request can be canceled
|
||||
verifyExecuteCommandSeqIsCancellable(<protocol.NavBarRequest>{
|
||||
command: "navbar",
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
|
||||
// send outlining spans request (normal priority)
|
||||
session.executeCommandSeq(<protocol.OutliningSpansRequestFull>{
|
||||
command: "outliningSpans",
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
|
||||
// ensure the outlining spans request can be canceled
|
||||
verifyExecuteCommandSeqIsCancellable(<protocol.OutliningSpansRequestFull>{
|
||||
command: "outliningSpans",
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
}
|
||||
|
||||
function verifyExecuteCommandSeqIsCancellable<T extends server.protocol.Request>(request: Partial<T>) {
|
||||
// Set the next request to be cancellable
|
||||
// The cancellation token will cancel the request the third time
|
||||
// isCancellationRequested() is called.
|
||||
cancellationToken.setRequestToCancel(session.getNextSeq());
|
||||
let operationCanceledExceptionThrown = false;
|
||||
|
||||
try {
|
||||
session.executeCommandSeq(request);
|
||||
}
|
||||
catch (e) {
|
||||
assert(e instanceof OperationCanceledException);
|
||||
operationCanceledExceptionThrown = true;
|
||||
}
|
||||
assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
122
src/testRunner/unittests/tsserver/completions.ts
Normal file
122
src/testRunner/unittests/tsserver/completions.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: completions", () => {
|
||||
it("works", () => {
|
||||
const aTs: File = {
|
||||
path: "/a.ts",
|
||||
content: "export const foo = 0;",
|
||||
};
|
||||
const bTs: File = {
|
||||
path: "/b.ts",
|
||||
content: "foo",
|
||||
};
|
||||
const tsconfig: File = {
|
||||
path: "/tsconfig.json",
|
||||
content: "{}",
|
||||
};
|
||||
|
||||
const session = createSession(createServerHost([aTs, bTs, tsconfig]));
|
||||
openFilesForSession([aTs, bTs], session);
|
||||
|
||||
const requestLocation: protocol.FileLocationRequestArgs = {
|
||||
file: bTs.path,
|
||||
line: 1,
|
||||
offset: 3,
|
||||
};
|
||||
|
||||
const response = executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, {
|
||||
...requestLocation,
|
||||
includeExternalModuleExports: true,
|
||||
prefix: "foo",
|
||||
});
|
||||
const entry: protocol.CompletionEntry = {
|
||||
hasAction: true,
|
||||
insertText: undefined,
|
||||
isRecommended: undefined,
|
||||
kind: ScriptElementKind.constElement,
|
||||
kindModifiers: ScriptElementKindModifier.exportedModifier,
|
||||
name: "foo",
|
||||
replacementSpan: undefined,
|
||||
sortText: "0",
|
||||
source: "/a",
|
||||
};
|
||||
assert.deepEqual<protocol.CompletionInfo | undefined>(response, {
|
||||
isGlobalCompletion: true,
|
||||
isMemberCompletion: false,
|
||||
isNewIdentifierLocation: false,
|
||||
entries: [entry],
|
||||
});
|
||||
|
||||
const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = {
|
||||
...requestLocation,
|
||||
entryNames: [{ name: "foo", source: "/a" }],
|
||||
};
|
||||
|
||||
const detailsResponse = executeSessionRequest<protocol.CompletionDetailsRequest, protocol.CompletionDetailsResponse>(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs);
|
||||
const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = {
|
||||
displayParts: [
|
||||
keywordPart(SyntaxKind.ConstKeyword),
|
||||
spacePart(),
|
||||
displayPart("foo", SymbolDisplayPartKind.localName),
|
||||
punctuationPart(SyntaxKind.ColonToken),
|
||||
spacePart(),
|
||||
displayPart("0", SymbolDisplayPartKind.stringLiteral),
|
||||
],
|
||||
documentation: emptyArray,
|
||||
kind: ScriptElementKind.constElement,
|
||||
kindModifiers: ScriptElementKindModifier.exportedModifier,
|
||||
name: "foo",
|
||||
source: [{ text: "./a", kind: "text" }],
|
||||
tags: undefined,
|
||||
};
|
||||
assert.deepEqual<ReadonlyArray<protocol.CompletionEntryDetails> | undefined>(detailsResponse, [
|
||||
{
|
||||
codeActions: [
|
||||
{
|
||||
description: `Import 'foo' from module "./a"`,
|
||||
changes: [
|
||||
{
|
||||
fileName: "/b.ts",
|
||||
textChanges: [
|
||||
{
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 1, offset: 1 },
|
||||
newText: 'import { foo } from "./a";\n\n',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
commands: undefined,
|
||||
},
|
||||
],
|
||||
...detailsCommon,
|
||||
},
|
||||
]);
|
||||
|
||||
interface CompletionDetailsFullRequest extends protocol.FileLocationRequest {
|
||||
readonly command: protocol.CommandTypes.CompletionDetailsFull;
|
||||
readonly arguments: protocol.CompletionDetailsRequestArgs;
|
||||
}
|
||||
interface CompletionDetailsFullResponse extends protocol.Response {
|
||||
readonly body?: ReadonlyArray<CompletionEntryDetails>;
|
||||
}
|
||||
const detailsFullResponse = executeSessionRequest<CompletionDetailsFullRequest, CompletionDetailsFullResponse>(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs);
|
||||
assert.deepEqual<ReadonlyArray<CompletionEntryDetails> | undefined>(detailsFullResponse, [
|
||||
{
|
||||
codeActions: [
|
||||
{
|
||||
description: `Import 'foo' from module "./a"`,
|
||||
changes: [
|
||||
{
|
||||
fileName: "/b.ts",
|
||||
textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')],
|
||||
},
|
||||
],
|
||||
commands: undefined,
|
||||
}
|
||||
],
|
||||
...detailsCommon,
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
174
src/testRunner/unittests/tsserver/configFileSearch.ts
Normal file
174
src/testRunner/unittests/tsserver/configFileSearch.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: searching for config file", () => {
|
||||
it("should stop at projectRootPath if given", () => {
|
||||
const f1 = {
|
||||
path: "/a/file1.ts",
|
||||
content: ""
|
||||
};
|
||||
const configFile = {
|
||||
path: "/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const host = createServerHost([f1, configFile]);
|
||||
const service = createProjectService(host);
|
||||
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a");
|
||||
|
||||
checkNumberOfConfiguredProjects(service, 0);
|
||||
checkNumberOfInferredProjects(service, 1);
|
||||
|
||||
service.closeClientFile(f1.path);
|
||||
service.openClientFile(f1.path);
|
||||
checkNumberOfConfiguredProjects(service, 1);
|
||||
checkNumberOfInferredProjects(service, 0);
|
||||
});
|
||||
|
||||
it("should use projectRootPath when searching for inferred project again", () => {
|
||||
const projectDir = "/a/b/projects/project";
|
||||
const configFileLocation = `${projectDir}/src`;
|
||||
const f1 = {
|
||||
path: `${configFileLocation}/file1.ts`,
|
||||
content: ""
|
||||
};
|
||||
const configFile = {
|
||||
path: `${configFileLocation}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const configFile2 = {
|
||||
path: "/a/b/projects/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const host = createServerHost([f1, libFile, configFile, configFile2]);
|
||||
const service = createProjectService(host);
|
||||
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir);
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
assert.isDefined(service.configuredProjects.get(configFile.path));
|
||||
checkWatchedFiles(host, [libFile.path, configFile.path]);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
const typeRootLocations = getTypeRootsFromLocation(configFileLocation);
|
||||
checkWatchedDirectories(host, typeRootLocations.concat(configFileLocation), /*recursive*/ true);
|
||||
|
||||
// Delete config file - should create inferred project and not configured project
|
||||
host.reloadFS([f1, libFile, configFile2]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkNumberOfProjects(service, { inferredProjects: 1 });
|
||||
checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
checkWatchedDirectories(host, typeRootLocations, /*recursive*/ true);
|
||||
});
|
||||
|
||||
it("should use projectRootPath when searching for inferred project again 2", () => {
|
||||
const projectDir = "/a/b/projects/project";
|
||||
const configFileLocation = `${projectDir}/src`;
|
||||
const f1 = {
|
||||
path: `${configFileLocation}/file1.ts`,
|
||||
content: ""
|
||||
};
|
||||
const configFile = {
|
||||
path: `${configFileLocation}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const configFile2 = {
|
||||
path: "/a/b/projects/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const host = createServerHost([f1, libFile, configFile, configFile2]);
|
||||
const service = createProjectService(host, { useSingleInferredProject: true }, { useInferredProjectPerProjectRoot: true });
|
||||
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir);
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
assert.isDefined(service.configuredProjects.get(configFile.path));
|
||||
checkWatchedFiles(host, [libFile.path, configFile.path]);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
checkWatchedDirectories(host, getTypeRootsFromLocation(configFileLocation).concat(configFileLocation), /*recursive*/ true);
|
||||
|
||||
// Delete config file - should create inferred project with project root path set
|
||||
host.reloadFS([f1, libFile, configFile2]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkNumberOfProjects(service, { inferredProjects: 1 });
|
||||
assert.equal(service.inferredProjects[0].projectRootPath, projectDir);
|
||||
checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
checkWatchedDirectories(host, getTypeRootsFromLocation(projectDir), /*recursive*/ true);
|
||||
});
|
||||
|
||||
describe("when the opened file is not from project root", () => {
|
||||
const projectRoot = "/a/b/projects/project";
|
||||
const file: File = {
|
||||
path: `${projectRoot}/src/index.ts`,
|
||||
content: "let y = 10"
|
||||
};
|
||||
const tsconfig: File = {
|
||||
path: `${projectRoot}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const files = [file, libFile];
|
||||
const filesWithConfig = files.concat(tsconfig);
|
||||
const dirOfFile = getDirectoryPath(file.path);
|
||||
|
||||
function openClientFile(files: File[]) {
|
||||
const host = createServerHost(files);
|
||||
const projectService = createProjectService(host);
|
||||
|
||||
projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj");
|
||||
return { host, projectService };
|
||||
}
|
||||
|
||||
function verifyConfiguredProject(host: TestServerHost, projectService: TestProjectService, orphanInferredProject?: boolean) {
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: orphanInferredProject ? 1 : 0 });
|
||||
const project = Debug.assertDefined(projectService.configuredProjects.get(tsconfig.path));
|
||||
|
||||
if (orphanInferredProject) {
|
||||
const inferredProject = projectService.inferredProjects[0];
|
||||
assert.isTrue(inferredProject.isOrphan());
|
||||
}
|
||||
|
||||
checkProjectActualFiles(project, [file.path, libFile.path, tsconfig.path]);
|
||||
checkWatchedFiles(host, [libFile.path, tsconfig.path]);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
checkWatchedDirectories(host, (orphanInferredProject ? [projectRoot, `${dirOfFile}/node_modules/@types`] : [projectRoot]).concat(getTypeRootsFromLocation(projectRoot)), /*recursive*/ true);
|
||||
}
|
||||
|
||||
function verifyInferredProject(host: TestServerHost, projectService: TestProjectService) {
|
||||
projectService.checkNumberOfProjects({ inferredProjects: 1 });
|
||||
const project = projectService.inferredProjects[0];
|
||||
assert.isDefined(project);
|
||||
|
||||
const filesToWatch = [libFile.path];
|
||||
forEachAncestorDirectory(dirOfFile, ancestor => {
|
||||
filesToWatch.push(combinePaths(ancestor, "tsconfig.json"));
|
||||
filesToWatch.push(combinePaths(ancestor, "jsconfig.json"));
|
||||
});
|
||||
|
||||
checkProjectActualFiles(project, [file.path, libFile.path]);
|
||||
checkWatchedFiles(host, filesToWatch);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
checkWatchedDirectories(host, getTypeRootsFromLocation(dirOfFile), /*recursive*/ true);
|
||||
}
|
||||
|
||||
it("tsconfig for the file exists", () => {
|
||||
const { host, projectService } = openClientFile(filesWithConfig);
|
||||
verifyConfiguredProject(host, projectService);
|
||||
|
||||
host.reloadFS(files);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyInferredProject(host, projectService);
|
||||
|
||||
host.reloadFS(filesWithConfig);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true);
|
||||
});
|
||||
|
||||
it("tsconfig for the file does not exist", () => {
|
||||
const { host, projectService } = openClientFile(files);
|
||||
verifyInferredProject(host, projectService);
|
||||
|
||||
host.reloadFS(filesWithConfig);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true);
|
||||
|
||||
host.reloadFS(files);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
verifyInferredProject(host, projectService);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
566
src/testRunner/unittests/tsserver/declarationFileMaps.ts
Normal file
566
src/testRunner/unittests/tsserver/declarationFileMaps.ts
Normal file
@@ -0,0 +1,566 @@
|
||||
namespace ts.projectSystem {
|
||||
function protocolFileSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileSpan {
|
||||
return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring, options) };
|
||||
}
|
||||
|
||||
function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan {
|
||||
return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) };
|
||||
}
|
||||
|
||||
function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation {
|
||||
return documentSpanFromSubstring(file, substring, options);
|
||||
}
|
||||
|
||||
function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem {
|
||||
return {
|
||||
...protocolFileSpanFromSubstring(file, text, options),
|
||||
isDefinition,
|
||||
isWriteAccess: isDefinition,
|
||||
lineText,
|
||||
};
|
||||
}
|
||||
|
||||
function makeReferenceEntry(file: File, isDefinition: boolean, text: string, options?: SpanFromSubstringOptions): ReferenceEntry {
|
||||
return {
|
||||
...documentSpanFromSubstring(file, text, options),
|
||||
isDefinition,
|
||||
isWriteAccess: isDefinition,
|
||||
isInString: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: ReadonlyArray<File>): void {
|
||||
openFilesForSession([file], session);
|
||||
const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false));
|
||||
const program = project.getCurrentProgram()!;
|
||||
const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true);
|
||||
closeFilesForSession([file], session);
|
||||
|
||||
Debug.assert(!output.emitSkipped);
|
||||
assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false })));
|
||||
}
|
||||
|
||||
describe("tsserver:: with declaration file maps:: project references", () => {
|
||||
const aTs: File = {
|
||||
path: "/a/a.ts",
|
||||
content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};",
|
||||
};
|
||||
const compilerOptions: CompilerOptions = {
|
||||
outDir: "bin",
|
||||
declaration: true,
|
||||
declarationMap: true,
|
||||
composite: true,
|
||||
};
|
||||
const configContent = JSON.stringify({ compilerOptions });
|
||||
const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent };
|
||||
|
||||
const aDtsMapContent: RawSourceMap = {
|
||||
version: 3,
|
||||
file: "a.d.ts",
|
||||
sourceRoot: "",
|
||||
sources: ["../a.ts"],
|
||||
names: [],
|
||||
mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC"
|
||||
};
|
||||
const aDtsMap: File = {
|
||||
path: "/a/bin/a.d.ts.map",
|
||||
content: JSON.stringify(aDtsMapContent),
|
||||
};
|
||||
const aDts: File = {
|
||||
path: "/a/bin/a.d.ts",
|
||||
// Need to mangle the sourceMappingURL part or it breaks the build
|
||||
content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`,
|
||||
};
|
||||
|
||||
const bTs: File = {
|
||||
path: "/b/b.ts",
|
||||
content: "export function fnB() {}",
|
||||
};
|
||||
const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent };
|
||||
|
||||
const bDtsMapContent: RawSourceMap = {
|
||||
version: 3,
|
||||
file: "b.d.ts",
|
||||
sourceRoot: "",
|
||||
sources: ["../b.ts"],
|
||||
names: [],
|
||||
mappings: "AAAA,wBAAgB,GAAG,SAAK",
|
||||
};
|
||||
const bDtsMap: File = {
|
||||
path: "/b/bin/b.d.ts.map",
|
||||
content: JSON.stringify(bDtsMapContent),
|
||||
};
|
||||
const bDts: File = {
|
||||
// Need to mangle the sourceMappingURL part or it breaks the build
|
||||
path: "/b/bin/b.d.ts",
|
||||
content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`,
|
||||
};
|
||||
|
||||
const dummyFile: File = {
|
||||
path: "/dummy/dummy.ts",
|
||||
content: "let a = 10;"
|
||||
};
|
||||
|
||||
const userTs: File = {
|
||||
path: "/user/user.ts",
|
||||
content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }',
|
||||
};
|
||||
|
||||
const userTsForConfigProject: File = {
|
||||
path: "/user/user.ts",
|
||||
content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }',
|
||||
};
|
||||
|
||||
const userTsconfig: File = {
|
||||
path: "/user/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
file: ["user.ts"],
|
||||
references: [{ path: "../a" }, { path: "../b" }]
|
||||
})
|
||||
};
|
||||
|
||||
function makeSampleProjects(addUserTsConfig?: boolean) {
|
||||
const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]);
|
||||
const session = createSession(host);
|
||||
|
||||
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
|
||||
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
|
||||
|
||||
// Testing what happens if we delete the original sources.
|
||||
host.deleteFile(bTs.path);
|
||||
|
||||
openFilesForSession([userTs], session);
|
||||
const service = session.getProjectService();
|
||||
checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 1 } : { inferredProjects: 1 });
|
||||
return session;
|
||||
}
|
||||
|
||||
function verifyInferredProjectUnchanged(session: TestSession) {
|
||||
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]);
|
||||
}
|
||||
|
||||
function verifyDummyProject(session: TestSession) {
|
||||
checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]);
|
||||
}
|
||||
|
||||
function verifyOnlyOrphanInferredProject(session: TestSession) {
|
||||
openFilesForSession([dummyFile], session);
|
||||
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
|
||||
verifyDummyProject(session);
|
||||
}
|
||||
|
||||
function verifySingleInferredProject(session: TestSession) {
|
||||
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 });
|
||||
verifyInferredProjectUnchanged(session);
|
||||
|
||||
// Close user file should close all the projects after opening dummy file
|
||||
closeFilesForSession([userTs], session);
|
||||
verifyOnlyOrphanInferredProject(session);
|
||||
}
|
||||
|
||||
function verifyATsConfigProject(session: TestSession) {
|
||||
checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]);
|
||||
}
|
||||
|
||||
function verifyATsConfigOriginalProject(session: TestSession) {
|
||||
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
|
||||
verifyInferredProjectUnchanged(session);
|
||||
verifyATsConfigProject(session);
|
||||
// Close user file should close all the projects
|
||||
closeFilesForSession([userTs], session);
|
||||
verifyOnlyOrphanInferredProject(session);
|
||||
}
|
||||
|
||||
function verifyATsConfigWhenOpened(session: TestSession) {
|
||||
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
|
||||
verifyInferredProjectUnchanged(session);
|
||||
verifyATsConfigProject(session);
|
||||
|
||||
closeFilesForSession([userTs], session);
|
||||
openFilesForSession([dummyFile], session);
|
||||
checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 });
|
||||
verifyDummyProject(session);
|
||||
verifyATsConfigProject(session); // ATsConfig should still be alive
|
||||
}
|
||||
|
||||
function verifyUserTsConfigProject(session: TestSession) {
|
||||
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aDts.path, userTsconfig.path]);
|
||||
}
|
||||
|
||||
it("goToDefinition", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()"));
|
||||
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]);
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
|
||||
it("getDefinitionAndBoundSpan", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()"));
|
||||
assert.deepEqual(response, {
|
||||
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
|
||||
definitions: [protocolFileSpanFromSubstring(aTs, "fnA")],
|
||||
});
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
|
||||
it("getDefinitionAndBoundSpan with file navigation", () => {
|
||||
const session = makeSampleProjects(/*addUserTsConfig*/ true);
|
||||
const response = executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()"));
|
||||
assert.deepEqual(response, {
|
||||
textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
|
||||
definitions: [protocolFileSpanFromSubstring(aTs, "fnA")],
|
||||
});
|
||||
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
|
||||
verifyUserTsConfigProject(session);
|
||||
|
||||
// Navigate to the definition
|
||||
closeFilesForSession([userTs], session);
|
||||
openFilesForSession([aTs], session);
|
||||
|
||||
// UserTs configured project should be alive
|
||||
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
|
||||
verifyUserTsConfigProject(session);
|
||||
verifyATsConfigProject(session);
|
||||
|
||||
closeFilesForSession([aTs], session);
|
||||
verifyOnlyOrphanInferredProject(session);
|
||||
});
|
||||
|
||||
it("goToType", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.TypeDefinitionRequest, protocol.TypeDefinitionResponse>(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA"));
|
||||
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "IfaceA")]);
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
|
||||
it("goToImplementation", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.ImplementationRequest, protocol.ImplementationResponse>(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()"));
|
||||
assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]);
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
|
||||
it("goToDefinition -- target does not exist", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()"));
|
||||
// bTs does not exist, so stick with bDts
|
||||
assert.deepEqual(response, [protocolFileSpanFromSubstring(bDts, "fnB")]);
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
|
||||
it("navigateTo", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" });
|
||||
assert.deepEqual<ReadonlyArray<protocol.NavtoItem> | undefined>(response, [
|
||||
{
|
||||
...protocolFileSpanFromSubstring(bDts, "export declare function fnB(): void;"),
|
||||
name: "fnB",
|
||||
matchKind: "prefix",
|
||||
isCaseSensitive: true,
|
||||
kind: ScriptElementKind.functionElement,
|
||||
kindModifiers: "export,declare",
|
||||
},
|
||||
{
|
||||
...protocolFileSpanFromSubstring(userTs, "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"),
|
||||
name: "fnUser",
|
||||
matchKind: "prefix",
|
||||
isCaseSensitive: true,
|
||||
kind: ScriptElementKind.functionElement,
|
||||
kindModifiers: "export",
|
||||
},
|
||||
{
|
||||
...protocolFileSpanFromSubstring(aTs, "export function fnA() {}"),
|
||||
name: "fnA",
|
||||
matchKind: "prefix",
|
||||
isCaseSensitive: true,
|
||||
kind: ScriptElementKind.functionElement,
|
||||
kindModifiers: "export",
|
||||
},
|
||||
]);
|
||||
|
||||
verifyATsConfigOriginalProject(session);
|
||||
});
|
||||
|
||||
const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem(aTs, /*isDefinition*/ true, "fnA", "export function fnA() {}");
|
||||
const referencesUserTs = (userTs: File): ReadonlyArray<protocol.ReferencesResponseItem> => [
|
||||
makeReferenceItem(userTs, /*isDefinition*/ false, "fnA", "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"),
|
||||
];
|
||||
|
||||
it("findAllReferences", () => {
|
||||
const session = makeSampleProjects();
|
||||
|
||||
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()"));
|
||||
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
|
||||
refs: [...referencesUserTs(userTs), referenceATs(aTs)],
|
||||
symbolName: "fnA",
|
||||
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset,
|
||||
symbolDisplayString: "function fnA(): void",
|
||||
});
|
||||
|
||||
verifyATsConfigOriginalProject(session);
|
||||
});
|
||||
|
||||
it("findAllReferences -- starting at definition", () => {
|
||||
const session = makeSampleProjects();
|
||||
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
|
||||
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA"));
|
||||
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
|
||||
refs: [referenceATs(aTs), ...referencesUserTs(userTs)],
|
||||
symbolName: "fnA",
|
||||
symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset,
|
||||
symbolDisplayString: "function fnA(): void",
|
||||
});
|
||||
verifyATsConfigWhenOpened(session);
|
||||
});
|
||||
|
||||
interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; }
|
||||
interface ReferencesFullResponse extends protocol.Response { readonly body: ReadonlyArray<ReferencedSymbol>; }
|
||||
|
||||
it("findAllReferencesFull", () => {
|
||||
const session = makeSampleProjects();
|
||||
|
||||
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
|
||||
|
||||
assert.deepEqual<ReadonlyArray<ReferencedSymbol>>(responseFull, [
|
||||
{
|
||||
definition: {
|
||||
...documentSpanFromSubstring(aTs, "fnA"),
|
||||
kind: ScriptElementKind.functionElement,
|
||||
name: "function fnA(): void",
|
||||
containerKind: ScriptElementKind.unknown,
|
||||
containerName: "",
|
||||
displayParts: [
|
||||
keywordPart(SyntaxKind.FunctionKeyword),
|
||||
spacePart(),
|
||||
displayPart("fnA", SymbolDisplayPartKind.functionName),
|
||||
punctuationPart(SyntaxKind.OpenParenToken),
|
||||
punctuationPart(SyntaxKind.CloseParenToken),
|
||||
punctuationPart(SyntaxKind.ColonToken),
|
||||
spacePart(),
|
||||
keywordPart(SyntaxKind.VoidKeyword),
|
||||
],
|
||||
},
|
||||
references: [
|
||||
makeReferenceEntry(userTs, /*isDefinition*/ false, "fnA"),
|
||||
makeReferenceEntry(aTs, /*isDefinition*/ true, "fnA"),
|
||||
],
|
||||
},
|
||||
]);
|
||||
verifyATsConfigOriginalProject(session);
|
||||
});
|
||||
|
||||
it("findAllReferencesFull definition is in mapped file", () => {
|
||||
const aTs: File = { path: "/a/a.ts", content: `function f() {}` };
|
||||
const aTsconfig: File = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }),
|
||||
};
|
||||
const bTs: File = { path: "/b/b.ts", content: `f();` };
|
||||
const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) };
|
||||
const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` };
|
||||
const aDtsMap: File = {
|
||||
path: "/bin/a.d.ts.map",
|
||||
content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }),
|
||||
};
|
||||
|
||||
const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap]));
|
||||
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
|
||||
openFilesForSession([bTs], session);
|
||||
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
|
||||
|
||||
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()"));
|
||||
|
||||
assert.deepEqual<ReadonlyArray<ReferencedSymbol>>(responseFull, [
|
||||
{
|
||||
definition: {
|
||||
containerKind: ScriptElementKind.unknown,
|
||||
containerName: "",
|
||||
displayParts: [
|
||||
keywordPart(SyntaxKind.FunctionKeyword),
|
||||
spacePart(),
|
||||
displayPart("f", SymbolDisplayPartKind.functionName),
|
||||
punctuationPart(SyntaxKind.OpenParenToken),
|
||||
punctuationPart(SyntaxKind.CloseParenToken),
|
||||
punctuationPart(SyntaxKind.ColonToken),
|
||||
spacePart(),
|
||||
keywordPart(SyntaxKind.VoidKeyword),
|
||||
],
|
||||
fileName: aTs.path,
|
||||
kind: ScriptElementKind.functionElement,
|
||||
name: "function f(): void",
|
||||
textSpan: { start: 9, length: 1 },
|
||||
},
|
||||
references: [
|
||||
{
|
||||
fileName: bTs.path,
|
||||
isDefinition: false,
|
||||
isInString: undefined,
|
||||
isWriteAccess: false,
|
||||
textSpan: { start: 0, length: 1 },
|
||||
},
|
||||
{
|
||||
fileName: aTs.path,
|
||||
isDefinition: true,
|
||||
isInString: undefined,
|
||||
isWriteAccess: true,
|
||||
textSpan: { start: 9, length: 1 },
|
||||
},
|
||||
],
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("findAllReferences -- target does not exist", () => {
|
||||
const session = makeSampleProjects();
|
||||
|
||||
const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()"));
|
||||
assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, {
|
||||
refs: [
|
||||
makeReferenceItem(bDts, /*isDefinition*/ true, "fnB", "export declare function fnB(): void;"),
|
||||
makeReferenceItem(userTs, /*isDefinition*/ false, "fnB", "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"),
|
||||
],
|
||||
symbolName: "fnB",
|
||||
symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset,
|
||||
symbolDisplayString: "function fnB(): void",
|
||||
});
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
|
||||
const renameATs = (aTs: File): protocol.SpanGroup => ({
|
||||
file: aTs.path,
|
||||
locs: [protocolRenameSpanFromSubstring(aTs.content, "fnA")],
|
||||
});
|
||||
const renameUserTs = (userTs: File): protocol.SpanGroup => ({
|
||||
file: userTs.path,
|
||||
locs: [protocolRenameSpanFromSubstring(userTs.content, "fnA")],
|
||||
});
|
||||
|
||||
it("renameLocations", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()"));
|
||||
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
|
||||
info: {
|
||||
canRename: true,
|
||||
displayName: "fnA",
|
||||
fileToRename: undefined,
|
||||
fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path.
|
||||
kind: ScriptElementKind.functionElement,
|
||||
kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","),
|
||||
triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"),
|
||||
},
|
||||
locs: [renameUserTs(userTs), renameATs(aTs)],
|
||||
});
|
||||
verifyATsConfigOriginalProject(session);
|
||||
});
|
||||
|
||||
it("renameLocations -- starting at definition", () => {
|
||||
const session = makeSampleProjects();
|
||||
openFilesForSession([aTs], session); // If it's not opened, the reference isn't found.
|
||||
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA"));
|
||||
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
|
||||
info: {
|
||||
canRename: true,
|
||||
displayName: "fnA",
|
||||
fileToRename: undefined,
|
||||
fullDisplayName: '"/a/a".fnA',
|
||||
kind: ScriptElementKind.functionElement,
|
||||
kindModifiers: ScriptElementKindModifier.exportedModifier,
|
||||
triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"),
|
||||
},
|
||||
locs: [renameATs(aTs), renameUserTs(userTs)],
|
||||
});
|
||||
verifyATsConfigWhenOpened(session);
|
||||
});
|
||||
|
||||
it("renameLocationsFull", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.RenameFullRequest, protocol.RenameFullResponse>(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()"));
|
||||
assert.deepEqual<ReadonlyArray<RenameLocation>>(response, [
|
||||
renameLocation(userTs, "fnA"),
|
||||
renameLocation(aTs, "fnA"),
|
||||
]);
|
||||
verifyATsConfigOriginalProject(session);
|
||||
});
|
||||
|
||||
it("renameLocations -- target does not exist", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()"));
|
||||
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
|
||||
info: {
|
||||
canRename: true,
|
||||
displayName: "fnB",
|
||||
fileToRename: undefined,
|
||||
fullDisplayName: '"/b/bin/b".fnB',
|
||||
kind: ScriptElementKind.functionElement,
|
||||
kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","),
|
||||
triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"),
|
||||
},
|
||||
locs: [
|
||||
{
|
||||
file: bDts.path,
|
||||
locs: [protocolRenameSpanFromSubstring(bDts.content, "fnB")],
|
||||
},
|
||||
{
|
||||
file: userTs.path,
|
||||
locs: [protocolRenameSpanFromSubstring(userTs.content, "fnB")],
|
||||
},
|
||||
],
|
||||
});
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
|
||||
it("getEditsForFileRename", () => {
|
||||
const session = makeSampleProjects();
|
||||
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, protocol.CommandTypes.GetEditsForFileRename, {
|
||||
oldFilePath: aTs.path,
|
||||
newFilePath: "/a/aNew.ts",
|
||||
});
|
||||
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, [
|
||||
{
|
||||
fileName: userTs.path,
|
||||
textChanges: [
|
||||
{ ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
verifySingleInferredProject(session);
|
||||
});
|
||||
|
||||
it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => {
|
||||
const aTs: File = { path: "/a/src/a.ts", content: "" };
|
||||
const aTsconfig: File = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
declaration: true,
|
||||
declarationMap: true,
|
||||
outDir: "./build",
|
||||
}
|
||||
}),
|
||||
};
|
||||
const bTs: File = { path: "/b/src/b.ts", content: "" };
|
||||
const bTsconfig: File = {
|
||||
path: "/b/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
outDir: "./build",
|
||||
},
|
||||
include: ["./src"],
|
||||
references: [{ path: "../a" }],
|
||||
}),
|
||||
};
|
||||
|
||||
const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([aTs, bTs], session);
|
||||
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, {
|
||||
oldFilePath: aTs.path,
|
||||
newFilePath: "/a/src/a1.ts",
|
||||
});
|
||||
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, []); // Should not change anything
|
||||
});
|
||||
});
|
||||
}
|
||||
94
src/testRunner/unittests/tsserver/documentRegistry.ts
Normal file
94
src/testRunner/unittests/tsserver/documentRegistry.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: document registry in project service", () => {
|
||||
const projectRootPath = "/user/username/projects/project";
|
||||
const importModuleContent = `import {a} from "./module1"`;
|
||||
const file: File = {
|
||||
path: `${projectRootPath}/index.ts`,
|
||||
content: importModuleContent
|
||||
};
|
||||
const moduleFile: File = {
|
||||
path: `${projectRootPath}/module1.d.ts`,
|
||||
content: "export const a: number;"
|
||||
};
|
||||
const configFile: File = {
|
||||
path: `${projectRootPath}/tsconfig.json`,
|
||||
content: JSON.stringify({ files: ["index.ts"] })
|
||||
};
|
||||
|
||||
function getProject(service: TestProjectService) {
|
||||
return service.configuredProjects.get(configFile.path)!;
|
||||
}
|
||||
|
||||
function checkProject(service: TestProjectService, moduleIsOrphan: boolean) {
|
||||
// Update the project
|
||||
const project = getProject(service);
|
||||
project.getLanguageService();
|
||||
checkProjectActualFiles(project, [file.path, libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]);
|
||||
const moduleInfo = service.getScriptInfo(moduleFile.path)!;
|
||||
assert.isDefined(moduleInfo);
|
||||
assert.equal(moduleInfo.isOrphan(), moduleIsOrphan);
|
||||
const key = service.documentRegistry.getKeyForCompilationSettings(project.getCompilationSettings());
|
||||
assert.deepEqual(service.documentRegistry.getLanguageServiceRefCounts(moduleInfo.path), [[key, moduleIsOrphan ? undefined : 1]]);
|
||||
}
|
||||
|
||||
function createServiceAndHost() {
|
||||
const host = createServerHost([file, moduleFile, libFile, configFile]);
|
||||
const service = createProjectService(host);
|
||||
service.openClientFile(file.path);
|
||||
checkProject(service, /*moduleIsOrphan*/ false);
|
||||
return { host, service };
|
||||
}
|
||||
|
||||
function changeFileToNotImportModule(service: TestProjectService) {
|
||||
const info = service.getScriptInfo(file.path)!;
|
||||
service.applyChangesToFile(info, [{ span: { start: 0, length: importModuleContent.length }, newText: "" }]);
|
||||
checkProject(service, /*moduleIsOrphan*/ true);
|
||||
}
|
||||
|
||||
function changeFileToImportModule(service: TestProjectService) {
|
||||
const info = service.getScriptInfo(file.path)!;
|
||||
service.applyChangesToFile(info, [{ span: { start: 0, length: 0 }, newText: importModuleContent }]);
|
||||
checkProject(service, /*moduleIsOrphan*/ false);
|
||||
}
|
||||
|
||||
it("Caches the source file if script info is orphan", () => {
|
||||
const { service } = createServiceAndHost();
|
||||
const project = getProject(service);
|
||||
|
||||
const moduleInfo = service.getScriptInfo(moduleFile.path)!;
|
||||
const sourceFile = moduleInfo.cacheSourceFile!.sourceFile;
|
||||
assert.equal(project.getSourceFile(moduleInfo.path), sourceFile);
|
||||
|
||||
// edit file
|
||||
changeFileToNotImportModule(service);
|
||||
assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile);
|
||||
|
||||
// write content back
|
||||
changeFileToImportModule(service);
|
||||
assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile);
|
||||
assert.equal(project.getSourceFile(moduleInfo.path), sourceFile);
|
||||
});
|
||||
|
||||
it("Caches the source file if script info is orphan, and orphan script info changes", () => {
|
||||
const { host, service } = createServiceAndHost();
|
||||
const project = getProject(service);
|
||||
|
||||
const moduleInfo = service.getScriptInfo(moduleFile.path)!;
|
||||
const sourceFile = moduleInfo.cacheSourceFile!.sourceFile;
|
||||
assert.equal(project.getSourceFile(moduleInfo.path), sourceFile);
|
||||
|
||||
// edit file
|
||||
changeFileToNotImportModule(service);
|
||||
assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile);
|
||||
|
||||
const updatedModuleContent = moduleFile.content + "\nexport const b: number;";
|
||||
host.writeFile(moduleFile.path, updatedModuleContent);
|
||||
|
||||
// write content back
|
||||
changeFileToImportModule(service);
|
||||
assert.notEqual(moduleInfo.cacheSourceFile!.sourceFile, sourceFile);
|
||||
assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile!.sourceFile);
|
||||
assert.equal(moduleInfo.cacheSourceFile!.sourceFile.text, updatedModuleContent);
|
||||
});
|
||||
});
|
||||
}
|
||||
54
src/testRunner/unittests/tsserver/duplicatePackages.ts
Normal file
54
src/testRunner/unittests/tsserver/duplicatePackages.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: duplicate packages", () => {
|
||||
// Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier.
|
||||
it("works with import fixes", () => {
|
||||
const packageContent = "export const foo: number;";
|
||||
const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" });
|
||||
const aFooIndex: File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent };
|
||||
const aFooPackage: File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent };
|
||||
const bFooIndex: File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent };
|
||||
const bFooPackage: File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent };
|
||||
|
||||
const userContent = 'import("foo");\nfoo';
|
||||
const aUser: File = { path: "/a/user.ts", content: userContent };
|
||||
const bUser: File = { path: "/b/user.ts", content: userContent };
|
||||
const tsconfig: File = {
|
||||
path: "/tsconfig.json",
|
||||
content: "{}",
|
||||
};
|
||||
|
||||
const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]);
|
||||
const session = createSession(host);
|
||||
|
||||
openFilesForSession([aUser, bUser], session);
|
||||
|
||||
for (const user of [aUser, bUser]) {
|
||||
const response = executeSessionRequest<protocol.CodeFixRequest, protocol.CodeFixResponse>(session, protocol.CommandTypes.GetCodeFixes, {
|
||||
file: user.path,
|
||||
startLine: 2,
|
||||
startOffset: 1,
|
||||
endLine: 2,
|
||||
endOffset: 4,
|
||||
errorCodes: [Diagnostics.Cannot_find_name_0.code],
|
||||
});
|
||||
assert.deepEqual<ReadonlyArray<protocol.CodeFixAction> | undefined>(response, [
|
||||
{
|
||||
description: `Import 'foo' from module "foo"`,
|
||||
fixName: "import",
|
||||
fixId: "fixMissingImport",
|
||||
fixAllDescription: "Add all missing imports",
|
||||
changes: [{
|
||||
fileName: user.path,
|
||||
textChanges: [{
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 1, offset: 1 },
|
||||
newText: 'import { foo } from "foo";\n\n',
|
||||
}],
|
||||
}],
|
||||
commands: undefined,
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: forceConsistentCasingInFileNames", () => {
|
||||
it("works when extends is specified with a case insensitive file system", () => {
|
||||
const rootPath = "/Users/username/dev/project";
|
||||
const file1: File = {
|
||||
path: `${rootPath}/index.ts`,
|
||||
content: 'import {x} from "file2";',
|
||||
};
|
||||
const file2: File = {
|
||||
path: `${rootPath}/file2.js`,
|
||||
content: "",
|
||||
};
|
||||
const file2Dts: File = {
|
||||
path: `${rootPath}/types/file2/index.d.ts`,
|
||||
content: "export declare const x: string;",
|
||||
};
|
||||
const tsconfigAll: File = {
|
||||
path: `${rootPath}/tsconfig.all.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: ".",
|
||||
paths: { file2: ["./file2.js"] },
|
||||
typeRoots: ["./types"],
|
||||
forceConsistentCasingInFileNames: true,
|
||||
},
|
||||
}),
|
||||
};
|
||||
const tsconfig: File = {
|
||||
path: `${rootPath}/tsconfig.json`,
|
||||
content: JSON.stringify({ extends: "./tsconfig.all.json" }),
|
||||
};
|
||||
|
||||
const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false });
|
||||
const session = createSession(host);
|
||||
|
||||
openFilesForSession([file1], session);
|
||||
const projectService = session.getProjectService();
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
|
||||
const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics();
|
||||
assert.deepEqual(diagnostics, []);
|
||||
});
|
||||
});
|
||||
}
|
||||
39
src/testRunner/unittests/tsserver/formatSettings.ts
Normal file
39
src/testRunner/unittests/tsserver/formatSettings.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: format settings", () => {
|
||||
it("can be set globally", () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let x;"
|
||||
};
|
||||
const host = createServerHost([f1]);
|
||||
const projectService = createProjectService(host);
|
||||
projectService.openClientFile(f1.path);
|
||||
|
||||
const defaultSettings = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath);
|
||||
|
||||
// set global settings
|
||||
const newGlobalSettings1 = { ...defaultSettings, placeOpenBraceOnNewLineForControlBlocks: !defaultSettings.placeOpenBraceOnNewLineForControlBlocks };
|
||||
projectService.setHostConfiguration({ formatOptions: newGlobalSettings1 });
|
||||
|
||||
// get format options for file - should be equal to new global settings
|
||||
const s1 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path));
|
||||
assert.deepEqual(s1, newGlobalSettings1, "file settings should be the same with global settings");
|
||||
|
||||
// set per file format options
|
||||
const newPerFileSettings = { ...defaultSettings, insertSpaceAfterCommaDelimiter: !defaultSettings.insertSpaceAfterCommaDelimiter };
|
||||
projectService.setHostConfiguration({ formatOptions: newPerFileSettings, file: f1.path });
|
||||
|
||||
// get format options for file - should be equal to new per-file settings
|
||||
const s2 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path));
|
||||
assert.deepEqual(s2, newPerFileSettings, "file settings should be the same with per-file settings");
|
||||
|
||||
// set new global settings - they should not affect ones that were set per-file
|
||||
const newGlobalSettings2 = { ...defaultSettings, insertSpaceAfterSemicolonInForStatements: !defaultSettings.insertSpaceAfterSemicolonInForStatements };
|
||||
projectService.setHostConfiguration({ formatOptions: newGlobalSettings2 });
|
||||
|
||||
// get format options for file - should be equal to new per-file settings
|
||||
const s3 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path));
|
||||
assert.deepEqual(s3, newPerFileSettings, "file settings should still be the same with per-file settings");
|
||||
});
|
||||
});
|
||||
}
|
||||
105
src/testRunner/unittests/tsserver/getEditsForFileRename.ts
Normal file
105
src/testRunner/unittests/tsserver/getEditsForFileRename.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: getEditsForFileRename", () => {
|
||||
it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => {
|
||||
const userTs: File = {
|
||||
path: "/user.ts",
|
||||
content: 'import { x } from "./old";',
|
||||
};
|
||||
const newTs: File = {
|
||||
path: "/new.ts",
|
||||
content: "export const x = 0;",
|
||||
};
|
||||
const tsconfig: File = {
|
||||
path: "/tsconfig.json",
|
||||
content: "{}",
|
||||
};
|
||||
|
||||
const host = createServerHost([userTs, newTs, tsconfig]);
|
||||
const projectService = createProjectService(host);
|
||||
projectService.openClientFile(userTs.path);
|
||||
const project = projectService.configuredProjects.get(tsconfig.path)!;
|
||||
|
||||
Debug.assert(!!project.resolveModuleNames);
|
||||
|
||||
const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatSettings, emptyOptions);
|
||||
assert.deepEqual<ReadonlyArray<FileTextChanges>>(edits, [{
|
||||
fileName: "/user.ts",
|
||||
textChanges: [{
|
||||
span: textSpanFromSubstring(userTs.content, "./old"),
|
||||
newText: "./new",
|
||||
}],
|
||||
}]);
|
||||
});
|
||||
|
||||
it("works with multiple projects", () => {
|
||||
const aUserTs: File = {
|
||||
path: "/a/user.ts",
|
||||
content: 'import { x } from "./old";',
|
||||
};
|
||||
const aOldTs: File = {
|
||||
path: "/a/old.ts",
|
||||
content: "export const x = 0;",
|
||||
};
|
||||
const aTsconfig: File = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }),
|
||||
};
|
||||
const bUserTs: File = {
|
||||
path: "/b/user.ts",
|
||||
content: 'import { x } from "../a/old";',
|
||||
};
|
||||
const bTsconfig: File = {
|
||||
path: "/b/tsconfig.json",
|
||||
content: "{}",
|
||||
};
|
||||
|
||||
const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([aUserTs, bUserTs], session);
|
||||
|
||||
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, {
|
||||
oldFilePath: aOldTs.path,
|
||||
newFilePath: "/a/new.ts",
|
||||
});
|
||||
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, [
|
||||
{
|
||||
fileName: aTsconfig.path,
|
||||
textChanges: [{ ...protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }],
|
||||
},
|
||||
{
|
||||
fileName: aUserTs.path,
|
||||
textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }],
|
||||
},
|
||||
{
|
||||
fileName: bUserTs.path,
|
||||
textChanges: [{ ...protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("works with file moved to inferred project", () => {
|
||||
const aTs: File = { path: "/a.ts", content: 'import {} from "./b";' };
|
||||
const cTs: File = { path: "/c.ts", content: "export {};" };
|
||||
const tsconfig: File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) };
|
||||
|
||||
const host = createServerHost([aTs, cTs, tsconfig]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([aTs, cTs], session);
|
||||
|
||||
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, {
|
||||
oldFilePath: "/b.ts",
|
||||
newFilePath: cTs.path,
|
||||
});
|
||||
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, [
|
||||
{
|
||||
fileName: "/tsconfig.json",
|
||||
textChanges: [{ ...protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }],
|
||||
},
|
||||
{
|
||||
fileName: "/a.ts",
|
||||
textChanges: [{ ...protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -15,6 +15,9 @@ namespace ts.projectSystem {
|
||||
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
|
||||
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
|
||||
|
||||
export import commonFile1 = tscWatch.commonFile1;
|
||||
export import commonFile2 = tscWatch.commonFile2;
|
||||
|
||||
const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/;
|
||||
export function mapOutputToJson(s: string) {
|
||||
return convertToObject(
|
||||
@@ -459,9 +462,13 @@ namespace ts.projectSystem {
|
||||
return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes);
|
||||
}
|
||||
|
||||
//function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) {
|
||||
// checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path));
|
||||
//}
|
||||
export function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) {
|
||||
checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path));
|
||||
}
|
||||
|
||||
export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: ReadonlyArray<string>) {
|
||||
checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
|
||||
}
|
||||
|
||||
export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location {
|
||||
const start = str.indexOf(substring);
|
||||
@@ -497,18 +504,10 @@ namespace ts.projectSystem {
|
||||
Debug.assert(start !== -1);
|
||||
return createTextSpan(start, substring.length);
|
||||
}
|
||||
//function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs {
|
||||
// return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) };
|
||||
//}
|
||||
//function protocolFileSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileSpan {
|
||||
// return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring, options) };
|
||||
//}
|
||||
//function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan {
|
||||
// return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) };
|
||||
//}
|
||||
//function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation {
|
||||
// return documentSpanFromSubstring(file, substring, options);
|
||||
//}
|
||||
|
||||
export function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs {
|
||||
return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) };
|
||||
}
|
||||
|
||||
export interface SpanFromSubstringOptions {
|
||||
readonly index: number;
|
||||
@@ -648,33 +647,4 @@ namespace ts.projectSystem {
|
||||
assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs));
|
||||
}
|
||||
}
|
||||
|
||||
//function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem {
|
||||
// return {
|
||||
// ...protocolFileSpanFromSubstring(file, text, options),
|
||||
// isDefinition,
|
||||
// isWriteAccess: isDefinition,
|
||||
// lineText,
|
||||
// };
|
||||
//}
|
||||
|
||||
//function makeReferenceEntry(file: File, isDefinition: boolean, text: string, options?: SpanFromSubstringOptions): ReferenceEntry {
|
||||
// return {
|
||||
// ...documentSpanFromSubstring(file, text, options),
|
||||
// isDefinition,
|
||||
// isWriteAccess: isDefinition,
|
||||
// isInString: undefined,
|
||||
// };
|
||||
//}
|
||||
|
||||
//function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: ReadonlyArray<File>): void {
|
||||
// openFilesForSession([file], session);
|
||||
// const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false));
|
||||
// const program = project.getCurrentProgram()!;
|
||||
// const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true);
|
||||
// closeFilesForSession([file], session);
|
||||
|
||||
// Debug.assert(!output.emitSkipped);
|
||||
// assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false })));
|
||||
//}
|
||||
}
|
||||
|
||||
18
src/testRunner/unittests/tsserver/importHelpers.ts
Normal file
18
src/testRunner/unittests/tsserver/importHelpers.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: import helpers", () => {
|
||||
it("should not crash in tsserver", () => {
|
||||
const f1 = {
|
||||
path: "/a/app.ts",
|
||||
content: "export async function foo() { return 100; }"
|
||||
};
|
||||
const tslib = {
|
||||
path: "/a/node_modules/tslib/index.d.ts",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([f1, tslib]);
|
||||
const service = createProjectService(host);
|
||||
service.openExternalProject({ projectFileName: "p", rootFiles: [toExternalFile(f1.path)], options: { importHelpers: true } });
|
||||
service.checkNumberOfProjects({ externalProjects: 1 });
|
||||
});
|
||||
});
|
||||
}
|
||||
229
src/testRunner/unittests/tsserver/inferredProjects.ts
Normal file
229
src/testRunner/unittests/tsserver/inferredProjects.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: Inferred projects", () => {
|
||||
it("should support files without extensions", () => {
|
||||
const f = {
|
||||
path: "/a/compile",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const host = createServerHost([f]);
|
||||
const session = createSession(host);
|
||||
session.executeCommand(<server.protocol.SetCompilerOptionsForInferredProjectsRequest>{
|
||||
seq: 1,
|
||||
type: "request",
|
||||
command: "compilerOptionsForInferredProjects",
|
||||
arguments: {
|
||||
options: {
|
||||
allowJs: true
|
||||
}
|
||||
}
|
||||
});
|
||||
session.executeCommand(<server.protocol.OpenRequest>{
|
||||
seq: 2,
|
||||
type: "request",
|
||||
command: "open",
|
||||
arguments: {
|
||||
file: f.path,
|
||||
fileContent: f.content,
|
||||
scriptKindName: "JS"
|
||||
}
|
||||
});
|
||||
const projectService = session.getProjectService();
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [f.path]);
|
||||
});
|
||||
|
||||
it("inferred projects per project root", () => {
|
||||
const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" };
|
||||
const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" };
|
||||
const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" };
|
||||
const file4 = { path: "/c/file3.ts", content: "let z = 4;" };
|
||||
const host = createServerHost([file1, file2, file3, file4]);
|
||||
const session = createSession(host, {
|
||||
useSingleInferredProject: true,
|
||||
useInferredProjectPerProjectRoot: true
|
||||
});
|
||||
session.executeCommand(<server.protocol.SetCompilerOptionsForInferredProjectsRequest>{
|
||||
seq: 1,
|
||||
type: "request",
|
||||
command: CommandNames.CompilerOptionsForInferredProjects,
|
||||
arguments: {
|
||||
options: {
|
||||
allowJs: true,
|
||||
target: ScriptTarget.ESNext
|
||||
}
|
||||
}
|
||||
});
|
||||
session.executeCommand(<server.protocol.SetCompilerOptionsForInferredProjectsRequest>{
|
||||
seq: 2,
|
||||
type: "request",
|
||||
command: CommandNames.CompilerOptionsForInferredProjects,
|
||||
arguments: {
|
||||
options: {
|
||||
allowJs: true,
|
||||
target: ScriptTarget.ES2015
|
||||
},
|
||||
projectRootPath: "/b"
|
||||
}
|
||||
});
|
||||
session.executeCommand(<server.protocol.OpenRequest>{
|
||||
seq: 3,
|
||||
type: "request",
|
||||
command: CommandNames.Open,
|
||||
arguments: {
|
||||
file: file1.path,
|
||||
fileContent: file1.content,
|
||||
scriptKindName: "JS",
|
||||
projectRootPath: file1.projectRootPath
|
||||
}
|
||||
});
|
||||
session.executeCommand(<server.protocol.OpenRequest>{
|
||||
seq: 4,
|
||||
type: "request",
|
||||
command: CommandNames.Open,
|
||||
arguments: {
|
||||
file: file2.path,
|
||||
fileContent: file2.content,
|
||||
scriptKindName: "JS",
|
||||
projectRootPath: file2.projectRootPath
|
||||
}
|
||||
});
|
||||
session.executeCommand(<server.protocol.OpenRequest>{
|
||||
seq: 5,
|
||||
type: "request",
|
||||
command: CommandNames.Open,
|
||||
arguments: {
|
||||
file: file3.path,
|
||||
fileContent: file3.content,
|
||||
scriptKindName: "JS",
|
||||
projectRootPath: file3.projectRootPath
|
||||
}
|
||||
});
|
||||
session.executeCommand(<server.protocol.OpenRequest>{
|
||||
seq: 6,
|
||||
type: "request",
|
||||
command: CommandNames.Open,
|
||||
arguments: {
|
||||
file: file4.path,
|
||||
fileContent: file4.content,
|
||||
scriptKindName: "JS"
|
||||
}
|
||||
});
|
||||
|
||||
const projectService = session.getProjectService();
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 3 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]);
|
||||
checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]);
|
||||
checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]);
|
||||
assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext);
|
||||
assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext);
|
||||
assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015);
|
||||
});
|
||||
|
||||
function checkInferredProject(inferredProject: server.InferredProject, actualFiles: File[], target: ScriptTarget) {
|
||||
checkProjectActualFiles(inferredProject, actualFiles.map(f => f.path));
|
||||
assert.equal(inferredProject.getCompilationSettings().target, target);
|
||||
}
|
||||
|
||||
function verifyProjectRootWithCaseSensitivity(useCaseSensitiveFileNames: boolean) {
|
||||
const files: [File, File, File, File] = [
|
||||
{ path: "/a/file1.ts", content: "let x = 1;" },
|
||||
{ path: "/A/file2.ts", content: "let y = 2;" },
|
||||
{ path: "/b/file2.ts", content: "let x = 3;" },
|
||||
{ path: "/c/file3.ts", content: "let z = 4;" }
|
||||
];
|
||||
const host = createServerHost(files, { useCaseSensitiveFileNames });
|
||||
const projectService = createProjectService(host, { useSingleInferredProject: true, }, { useInferredProjectPerProjectRoot: true });
|
||||
projectService.setCompilerOptionsForInferredProjects({
|
||||
allowJs: true,
|
||||
target: ScriptTarget.ESNext
|
||||
});
|
||||
projectService.setCompilerOptionsForInferredProjects({
|
||||
allowJs: true,
|
||||
target: ScriptTarget.ES2015
|
||||
}, "/a");
|
||||
|
||||
openClientFiles(["/a", "/a", "/b", undefined]);
|
||||
verifyInferredProjectsState([
|
||||
[[files[3]], ScriptTarget.ESNext],
|
||||
[[files[0], files[1]], ScriptTarget.ES2015],
|
||||
[[files[2]], ScriptTarget.ESNext]
|
||||
]);
|
||||
closeClientFiles();
|
||||
|
||||
openClientFiles(["/a", "/A", "/b", undefined]);
|
||||
if (useCaseSensitiveFileNames) {
|
||||
verifyInferredProjectsState([
|
||||
[[files[3]], ScriptTarget.ESNext],
|
||||
[[files[0]], ScriptTarget.ES2015],
|
||||
[[files[1]], ScriptTarget.ESNext],
|
||||
[[files[2]], ScriptTarget.ESNext]
|
||||
]);
|
||||
}
|
||||
else {
|
||||
verifyInferredProjectsState([
|
||||
[[files[3]], ScriptTarget.ESNext],
|
||||
[[files[0], files[1]], ScriptTarget.ES2015],
|
||||
[[files[2]], ScriptTarget.ESNext]
|
||||
]);
|
||||
}
|
||||
closeClientFiles();
|
||||
|
||||
projectService.setCompilerOptionsForInferredProjects({
|
||||
allowJs: true,
|
||||
target: ScriptTarget.ES2017
|
||||
}, "/A");
|
||||
|
||||
openClientFiles(["/a", "/a", "/b", undefined]);
|
||||
verifyInferredProjectsState([
|
||||
[[files[3]], ScriptTarget.ESNext],
|
||||
[[files[0], files[1]], useCaseSensitiveFileNames ? ScriptTarget.ES2015 : ScriptTarget.ES2017],
|
||||
[[files[2]], ScriptTarget.ESNext]
|
||||
]);
|
||||
closeClientFiles();
|
||||
|
||||
openClientFiles(["/a", "/A", "/b", undefined]);
|
||||
if (useCaseSensitiveFileNames) {
|
||||
verifyInferredProjectsState([
|
||||
[[files[3]], ScriptTarget.ESNext],
|
||||
[[files[0]], ScriptTarget.ES2015],
|
||||
[[files[1]], ScriptTarget.ES2017],
|
||||
[[files[2]], ScriptTarget.ESNext]
|
||||
]);
|
||||
}
|
||||
else {
|
||||
verifyInferredProjectsState([
|
||||
[[files[3]], ScriptTarget.ESNext],
|
||||
[[files[0], files[1]], ScriptTarget.ES2017],
|
||||
[[files[2]], ScriptTarget.ESNext]
|
||||
]);
|
||||
}
|
||||
closeClientFiles();
|
||||
|
||||
function openClientFiles(projectRoots: [string | undefined, string | undefined, string | undefined, string | undefined]) {
|
||||
files.forEach((file, index) => {
|
||||
projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRoots[index]);
|
||||
});
|
||||
}
|
||||
|
||||
function closeClientFiles() {
|
||||
files.forEach(file => projectService.closeClientFile(file.path));
|
||||
}
|
||||
|
||||
function verifyInferredProjectsState(expected: [File[], ScriptTarget][]) {
|
||||
checkNumberOfProjects(projectService, { inferredProjects: expected.length });
|
||||
projectService.inferredProjects.forEach((p, index) => {
|
||||
const [actualFiles, target] = expected[index];
|
||||
checkInferredProject(p, actualFiles, target);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
it("inferred projects per project root with case sensitive system", () => {
|
||||
verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ true);
|
||||
});
|
||||
|
||||
it("inferred projects per project root with case insensitive system", () => {
|
||||
verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ false);
|
||||
});
|
||||
});
|
||||
}
|
||||
19
src/testRunner/unittests/tsserver/languageService.ts
Normal file
19
src/testRunner/unittests/tsserver/languageService.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: Language service", () => {
|
||||
it("should work correctly on case-sensitive file systems", () => {
|
||||
const lib = {
|
||||
path: "/a/Lib/lib.d.ts",
|
||||
content: "let x: number"
|
||||
};
|
||||
const f = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let x = 1;"
|
||||
};
|
||||
const host = createServerHost([lib, f], { executingFilePath: "/a/Lib/tsc.js", useCaseSensitiveFileNames: true });
|
||||
const projectService = createProjectService(host);
|
||||
projectService.openClientFile(f.path);
|
||||
projectService.checkNumberOfProjects({ inferredProjects: 1 });
|
||||
projectService.inferredProjects[0].getLanguageService().getProgram();
|
||||
});
|
||||
});
|
||||
}
|
||||
55
src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts
Normal file
55
src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: maxNodeModuleJsDepth for inferred projects", () => {
|
||||
it("should be set to 2 if the project has js root files", () => {
|
||||
const file1: File = {
|
||||
path: "/a/b/file1.js",
|
||||
content: `var t = require("test"); t.`
|
||||
};
|
||||
const moduleFile: File = {
|
||||
path: "/a/b/node_modules/test/index.js",
|
||||
content: `var v = 10; module.exports = v;`
|
||||
};
|
||||
|
||||
const host = createServerHost([file1, moduleFile]);
|
||||
const projectService = createProjectService(host);
|
||||
projectService.openClientFile(file1.path);
|
||||
|
||||
let project = projectService.inferredProjects[0];
|
||||
let options = project.getCompilationSettings();
|
||||
assert.isTrue(options.maxNodeModuleJsDepth === 2);
|
||||
|
||||
// Assert the option sticks
|
||||
projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 });
|
||||
project = projectService.inferredProjects[0];
|
||||
options = project.getCompilationSettings();
|
||||
assert.isTrue(options.maxNodeModuleJsDepth === 2);
|
||||
});
|
||||
|
||||
it("should return to normal state when all js root files are removed from project", () => {
|
||||
const file1 = {
|
||||
path: "/a/file1.ts",
|
||||
content: "let x =1;"
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/file2.js",
|
||||
content: "let x =1;"
|
||||
};
|
||||
|
||||
const host = createServerHost([file1, file2, libFile]);
|
||||
const projectService = createProjectService(host, { useSingleInferredProject: true });
|
||||
|
||||
projectService.openClientFile(file1.path);
|
||||
checkNumberOfInferredProjects(projectService, 1);
|
||||
let project = projectService.inferredProjects[0];
|
||||
assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth);
|
||||
|
||||
projectService.openClientFile(file2.path);
|
||||
project = projectService.inferredProjects[0];
|
||||
assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2);
|
||||
|
||||
projectService.closeClientFile(file2.path);
|
||||
project = projectService.inferredProjects[0];
|
||||
assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth);
|
||||
});
|
||||
});
|
||||
}
|
||||
99
src/testRunner/unittests/tsserver/metadataInResponse.ts
Normal file
99
src/testRunner/unittests/tsserver/metadataInResponse.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: with metadata in response", () => {
|
||||
const metadata = "Extra Info";
|
||||
function verifyOutput(host: TestServerHost, expectedResponse: protocol.Response) {
|
||||
const output = host.getOutput().map(mapOutputToJson);
|
||||
assert.deepEqual(output, [expectedResponse]);
|
||||
host.clearOutput();
|
||||
}
|
||||
|
||||
function verifyCommandWithMetadata<T extends server.protocol.Request, U = undefined>(session: TestSession, host: TestServerHost, command: Partial<T>, expectedResponseBody: U) {
|
||||
command.seq = session.getSeq();
|
||||
command.type = "request";
|
||||
session.onMessage(JSON.stringify(command));
|
||||
verifyOutput(host, expectedResponseBody ?
|
||||
{ seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: true, body: expectedResponseBody, metadata } :
|
||||
{ seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: false, message: "No content available." }
|
||||
);
|
||||
}
|
||||
|
||||
const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` };
|
||||
const tsconfig: File = {
|
||||
path: "/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions: { plugins: [{ name: "myplugin" }] }
|
||||
})
|
||||
};
|
||||
function createHostWithPlugin(files: ReadonlyArray<File>) {
|
||||
const host = createServerHost(files);
|
||||
host.require = (_initialPath, moduleName) => {
|
||||
assert.equal(moduleName, "myplugin");
|
||||
return {
|
||||
module: () => ({
|
||||
create(info: server.PluginCreateInfo) {
|
||||
const proxy = Harness.LanguageService.makeDefaultProxy(info);
|
||||
proxy.getCompletionsAtPosition = (filename, position, options) => {
|
||||
const result = info.languageService.getCompletionsAtPosition(filename, position, options);
|
||||
if (result) {
|
||||
result.metadata = metadata;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return proxy;
|
||||
}
|
||||
}),
|
||||
error: undefined
|
||||
};
|
||||
};
|
||||
return host;
|
||||
}
|
||||
|
||||
describe("With completion requests", () => {
|
||||
const completionRequestArgs: protocol.CompletionsRequestArgs = {
|
||||
file: aTs.path,
|
||||
line: 1,
|
||||
offset: aTs.content.indexOf("this.") + 1 + "this.".length
|
||||
};
|
||||
const expectedCompletionEntries: ReadonlyArray<protocol.CompletionEntry> = [
|
||||
{ name: "foo", kind: ScriptElementKind.memberFunctionElement, kindModifiers: "", sortText: "0" },
|
||||
{ name: "prop", kind: ScriptElementKind.memberVariableElement, kindModifiers: "", sortText: "0" }
|
||||
];
|
||||
|
||||
it("can pass through metadata when the command returns array", () => {
|
||||
const host = createHostWithPlugin([aTs, tsconfig]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([aTs], session);
|
||||
verifyCommandWithMetadata<protocol.CompletionsRequest, ReadonlyArray<protocol.CompletionEntry>>(session, host, {
|
||||
command: protocol.CommandTypes.Completions,
|
||||
arguments: completionRequestArgs
|
||||
}, expectedCompletionEntries);
|
||||
});
|
||||
|
||||
it("can pass through metadata when the command returns object", () => {
|
||||
const host = createHostWithPlugin([aTs, tsconfig]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([aTs], session);
|
||||
verifyCommandWithMetadata<protocol.CompletionsRequest, protocol.CompletionInfo>(session, host, {
|
||||
command: protocol.CommandTypes.CompletionInfo,
|
||||
arguments: completionRequestArgs
|
||||
}, {
|
||||
isGlobalCompletion: false,
|
||||
isMemberCompletion: true,
|
||||
isNewIdentifierLocation: false,
|
||||
entries: expectedCompletionEntries
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined correctly", () => {
|
||||
const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { const x = 0; } }` };
|
||||
const host = createHostWithPlugin([aTs, tsconfig]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([aTs], session);
|
||||
verifyCommandWithMetadata<protocol.CompletionsRequest>(session, host, {
|
||||
command: protocol.CommandTypes.Completions,
|
||||
arguments: { file: aTs.path, line: 1, offset: aTs.content.indexOf("x") + 1 }
|
||||
}, /*expectedResponseBody*/ undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
30
src/testRunner/unittests/tsserver/navTo.ts
Normal file
30
src/testRunner/unittests/tsserver/navTo.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: navigate-to for javascript project", () => {
|
||||
function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) {
|
||||
return find(items, item => item.name === itemName && item.kind === itemKind) !== undefined;
|
||||
}
|
||||
|
||||
it("should not include type symbols", () => {
|
||||
const file1: File = {
|
||||
path: "/a/b/file1.js",
|
||||
content: "function foo() {}"
|
||||
};
|
||||
const configFile: File = {
|
||||
path: "/a/b/jsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const host = createServerHost([file1, configFile, libFile]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([file1], session);
|
||||
|
||||
// Try to find some interface type defined in lib.d.ts
|
||||
const libTypeNavToRequest = makeSessionRequest<protocol.NavtoRequestArgs>(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path });
|
||||
const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[];
|
||||
assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`);
|
||||
|
||||
const localFunctionNavToRequst = makeSessionRequest<protocol.NavtoRequestArgs>(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path });
|
||||
const items2 = session.executeCommand(localFunctionNavToRequst).response as protocol.NavtoItem[];
|
||||
assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`);
|
||||
});
|
||||
});
|
||||
}
|
||||
47
src/testRunner/unittests/tsserver/occurences.ts
Normal file
47
src/testRunner/unittests/tsserver/occurences.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: occurence highlight on string", () => {
|
||||
it("should be marked if only on string values", () => {
|
||||
const file1: File = {
|
||||
path: "/a/b/file1.ts",
|
||||
content: `let t1 = "div";\nlet t2 = "div";\nlet t3 = { "div": 123 };\nlet t4 = t3["div"];`
|
||||
};
|
||||
|
||||
const host = createServerHost([file1]);
|
||||
const session = createSession(host);
|
||||
const projectService = session.getProjectService();
|
||||
|
||||
projectService.openClientFile(file1.path);
|
||||
{
|
||||
const highlightRequest = makeSessionRequest<protocol.FileLocationRequestArgs>(
|
||||
CommandNames.Occurrences,
|
||||
{ file: file1.path, line: 1, offset: 11 }
|
||||
);
|
||||
const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[];
|
||||
const firstOccurence = highlightResponse[0];
|
||||
assert.isTrue(firstOccurence.isInString, "Highlights should be marked with isInString");
|
||||
}
|
||||
|
||||
{
|
||||
const highlightRequest = makeSessionRequest<protocol.FileLocationRequestArgs>(
|
||||
CommandNames.Occurrences,
|
||||
{ file: file1.path, line: 3, offset: 13 }
|
||||
);
|
||||
const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[];
|
||||
assert.isTrue(highlightResponse.length === 2);
|
||||
const firstOccurence = highlightResponse[0];
|
||||
assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on property name");
|
||||
}
|
||||
|
||||
{
|
||||
const highlightRequest = makeSessionRequest<protocol.FileLocationRequestArgs>(
|
||||
CommandNames.Occurrences,
|
||||
{ file: file1.path, line: 4, offset: 14 }
|
||||
);
|
||||
const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[];
|
||||
assert.isTrue(highlightResponse.length === 2);
|
||||
const firstOccurence = highlightResponse[0];
|
||||
assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on indexer");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
108
src/testRunner/unittests/tsserver/openFile.ts
Normal file
108
src/testRunner/unittests/tsserver/openFile.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: Open-file", () => {
|
||||
it("can be reloaded with empty content", () => {
|
||||
const f = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const projectFileName = "externalProject";
|
||||
const host = createServerHost([f]);
|
||||
const projectService = createProjectService(host);
|
||||
// create a project
|
||||
projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} });
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
|
||||
const p = projectService.externalProjects[0];
|
||||
// force to load the content of the file
|
||||
p.updateGraph();
|
||||
|
||||
const scriptInfo = p.getScriptInfo(f.path)!;
|
||||
checkSnapLength(scriptInfo.getSnapshot(), f.content.length);
|
||||
|
||||
// open project and replace its content with empty string
|
||||
projectService.openClientFile(f.path, "");
|
||||
checkSnapLength(scriptInfo.getSnapshot(), 0);
|
||||
});
|
||||
function checkSnapLength(snap: IScriptSnapshot, expectedLength: number) {
|
||||
assert.equal(snap.getLength(), expectedLength, "Incorrect snapshot size");
|
||||
}
|
||||
|
||||
function verifyOpenFileWorks(useCaseSensitiveFileNames: boolean) {
|
||||
const file1: File = {
|
||||
path: "/a/b/src/app.ts",
|
||||
content: "let x = 10;"
|
||||
};
|
||||
const file2: File = {
|
||||
path: "/a/B/lib/module2.ts",
|
||||
content: "let z = 10;"
|
||||
};
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: ""
|
||||
};
|
||||
const configFile2: File = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([file1, file2, configFile, configFile2], {
|
||||
useCaseSensitiveFileNames
|
||||
});
|
||||
const service = createProjectService(host);
|
||||
|
||||
// Open file1 -> configFile
|
||||
verifyConfigFileName(file1, "/a", configFile);
|
||||
verifyConfigFileName(file1, "/a/b", configFile);
|
||||
verifyConfigFileName(file1, "/a/B", configFile);
|
||||
|
||||
// Open file2 use root "/a/b"
|
||||
verifyConfigFileName(file2, "/a", useCaseSensitiveFileNames ? configFile2 : configFile);
|
||||
verifyConfigFileName(file2, "/a/b", useCaseSensitiveFileNames ? configFile2 : configFile);
|
||||
verifyConfigFileName(file2, "/a/B", useCaseSensitiveFileNames ? undefined : configFile);
|
||||
|
||||
function verifyConfigFileName(file: File, projectRoot: string, expectedConfigFile: File | undefined) {
|
||||
const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot);
|
||||
assert.equal(configFileName, expectedConfigFile && expectedConfigFile.path);
|
||||
service.closeClientFile(file.path);
|
||||
}
|
||||
}
|
||||
it("works when project root is used with case-sensitive system", () => {
|
||||
verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ true);
|
||||
});
|
||||
|
||||
it("works when project root is used with case-insensitive system", () => {
|
||||
verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false);
|
||||
});
|
||||
|
||||
it("uses existing project even if project refresh is pending", () => {
|
||||
const projectFolder = "/user/someuser/projects/myproject";
|
||||
const aFile: File = {
|
||||
path: `${projectFolder}/src/a.ts`,
|
||||
content: "export const x = 0;"
|
||||
};
|
||||
const configFile: File = {
|
||||
path: `${projectFolder}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const files = [aFile, configFile, libFile];
|
||||
const host = createServerHost(files);
|
||||
const service = createProjectService(host);
|
||||
service.openClientFile(aFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder);
|
||||
verifyProject();
|
||||
|
||||
const bFile: File = {
|
||||
path: `${projectFolder}/src/b.ts`,
|
||||
content: `export {}; declare module "./a" { export const y: number; }`
|
||||
};
|
||||
files.push(bFile);
|
||||
host.reloadFS(files);
|
||||
service.openClientFile(bFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder);
|
||||
verifyProject();
|
||||
|
||||
function verifyProject() {
|
||||
assert.isDefined(service.configuredProjects.get(configFile.path));
|
||||
const project = service.configuredProjects.get(configFile.path)!;
|
||||
checkProjectActualFiles(project, files.map(f => f.path));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
658
src/testRunner/unittests/tsserver/projectReferences.ts
Normal file
658
src/testRunner/unittests/tsserver/projectReferences.ts
Normal file
@@ -0,0 +1,658 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: with project references and tsbuild", () => {
|
||||
function createHost(files: ReadonlyArray<File>, rootNames: ReadonlyArray<string>) {
|
||||
const host = createServerHost(files);
|
||||
|
||||
// ts build should succeed
|
||||
const solutionBuilder = tscWatch.createSolutionBuilder(host, rootNames, {});
|
||||
solutionBuilder.buildAllProjects();
|
||||
assert.equal(host.getOutput().length, 0);
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
describe("with container project", () => {
|
||||
function getProjectFiles(project: string): [File, File] {
|
||||
return [
|
||||
TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"),
|
||||
TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"),
|
||||
];
|
||||
}
|
||||
|
||||
const project = "container";
|
||||
const containerLib = getProjectFiles("container/lib");
|
||||
const containerExec = getProjectFiles("container/exec");
|
||||
const containerCompositeExec = getProjectFiles("container/compositeExec");
|
||||
const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json");
|
||||
const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig];
|
||||
|
||||
it("does not error on container only project", () => {
|
||||
const host = createHost(files, [containerConfig.path]);
|
||||
|
||||
// Open external project for the folder
|
||||
const session = createSession(host);
|
||||
const service = session.getProjectService();
|
||||
service.openExternalProjects([{
|
||||
projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project),
|
||||
rootFiles: files.map(f => ({ fileName: f.path })),
|
||||
options: {}
|
||||
}]);
|
||||
checkNumberOfProjects(service, { configuredProjects: 4 });
|
||||
files.forEach(f => {
|
||||
const args: protocol.FileRequestArgs = {
|
||||
file: f.path,
|
||||
projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined
|
||||
};
|
||||
const syntaxDiagnostics = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({
|
||||
command: protocol.CommandTypes.SyntacticDiagnosticsSync,
|
||||
arguments: args
|
||||
}).response;
|
||||
assert.deepEqual(syntaxDiagnostics, []);
|
||||
const semanticDiagnostics = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({
|
||||
command: protocol.CommandTypes.SemanticDiagnosticsSync,
|
||||
arguments: args
|
||||
}).response;
|
||||
assert.deepEqual(semanticDiagnostics, []);
|
||||
});
|
||||
const containerProject = service.configuredProjects.get(containerConfig.path)!;
|
||||
checkProjectActualFiles(containerProject, [containerConfig.path]);
|
||||
const optionsDiagnostics = session.executeCommandSeq<protocol.CompilerOptionsDiagnosticsRequest>({
|
||||
command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull,
|
||||
arguments: { projectFileName: containerProject.projectName }
|
||||
}).response;
|
||||
assert.deepEqual(optionsDiagnostics, []);
|
||||
});
|
||||
|
||||
it("can successfully find references with --out options", () => {
|
||||
const host = createHost(files, [containerConfig.path]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([containerCompositeExec[1]], session);
|
||||
const service = session.getProjectService();
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst");
|
||||
const response = session.executeCommandSeq<protocol.RenameRequest>({
|
||||
command: protocol.CommandTypes.Rename,
|
||||
arguments: {
|
||||
file: containerCompositeExec[1].path,
|
||||
...locationOfMyConst
|
||||
}
|
||||
}).response as protocol.RenameResponseBody;
|
||||
|
||||
|
||||
const myConstLen = "myConst".length;
|
||||
const locationOfMyConstInLib = protocolLocationFromSubstring(containerLib[1].content, "myConst");
|
||||
assert.deepEqual(response.locs, [
|
||||
{ file: containerCompositeExec[1].path, locs: [{ start: locationOfMyConst, end: { line: locationOfMyConst.line, offset: locationOfMyConst.offset + myConstLen } }] },
|
||||
{ file: containerLib[1].path, locs: [{ start: locationOfMyConstInLib, end: { line: locationOfMyConstInLib.line, offset: locationOfMyConstInLib.offset + myConstLen } }] }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with main and depedency project", () => {
|
||||
const projectLocation = "/user/username/projects/myproject";
|
||||
const dependecyLocation = `${projectLocation}/dependency`;
|
||||
const mainLocation = `${projectLocation}/main`;
|
||||
const dependencyTs: File = {
|
||||
path: `${dependecyLocation}/FnS.ts`,
|
||||
content: `export function fn1() { }
|
||||
export function fn2() { }
|
||||
export function fn3() { }
|
||||
export function fn4() { }
|
||||
export function fn5() { }
|
||||
`
|
||||
};
|
||||
const dependencyConfig: File = {
|
||||
path: `${dependecyLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true } })
|
||||
};
|
||||
|
||||
const mainTs: File = {
|
||||
path: `${mainLocation}/main.ts`,
|
||||
content: `import {
|
||||
fn1,
|
||||
fn2,
|
||||
fn3,
|
||||
fn4,
|
||||
fn5
|
||||
} from '../dependency/fns'
|
||||
|
||||
fn1();
|
||||
fn2();
|
||||
fn3();
|
||||
fn4();
|
||||
fn5();
|
||||
`
|
||||
};
|
||||
const mainConfig: File = {
|
||||
path: `${mainLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: { composite: true, declarationMap: true },
|
||||
references: [{ path: "../dependency" }]
|
||||
})
|
||||
};
|
||||
|
||||
const randomFile: File = {
|
||||
path: `${projectLocation}/random/random.ts`,
|
||||
content: "let a = 10;"
|
||||
};
|
||||
const randomConfig: File = {
|
||||
path: `${projectLocation}/random/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const dtsLocation = `${dependecyLocation}/FnS.d.ts`;
|
||||
const dtsPath = dtsLocation.toLowerCase() as Path;
|
||||
const dtsMapLocation = `${dtsLocation}.map`;
|
||||
const dtsMapPath = dtsMapLocation.toLowerCase() as Path;
|
||||
|
||||
const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig];
|
||||
|
||||
function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray<string>, closedInfos: ReadonlyArray<string>, otherWatchedFiles: ReadonlyArray<string>) {
|
||||
checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos));
|
||||
checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()));
|
||||
}
|
||||
|
||||
function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray<string>, closedInfos: ReadonlyArray<string>, otherWatchedFiles: ReadonlyArray<string>) {
|
||||
verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path));
|
||||
}
|
||||
|
||||
function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) {
|
||||
verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path]);
|
||||
}
|
||||
|
||||
// Returns request and expected Response, expected response when no map file
|
||||
interface SessionAction<Req = protocol.Request, Response = {}> {
|
||||
reqName: string;
|
||||
request: Partial<Req>;
|
||||
expectedResponse: Response;
|
||||
expectedResponseNoMap?: Response;
|
||||
expectedResponseNoDts?: Response;
|
||||
}
|
||||
function gotoDefintinionFromMainTs(fn: number): SessionAction<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> {
|
||||
const textSpan = usageSpan(fn);
|
||||
const definition: protocol.FileSpan = { file: dependencyTs.path, ...definitionSpan(fn) };
|
||||
const declareSpaceLength = "declare ".length;
|
||||
return {
|
||||
reqName: "goToDef",
|
||||
request: {
|
||||
command: protocol.CommandTypes.DefinitionAndBoundSpan,
|
||||
arguments: { file: mainTs.path, ...textSpan.start }
|
||||
},
|
||||
expectedResponse: {
|
||||
// To dependency
|
||||
definitions: [definition],
|
||||
textSpan
|
||||
},
|
||||
expectedResponseNoMap: {
|
||||
// To the dts
|
||||
definitions: [{ file: dtsPath, start: { line: fn, offset: definition.start.offset + declareSpaceLength }, end: { line: fn, offset: definition.end.offset + declareSpaceLength } }],
|
||||
textSpan
|
||||
},
|
||||
expectedResponseNoDts: {
|
||||
// To import declaration
|
||||
definitions: [{ file: mainTs.path, ...importSpan(fn) }],
|
||||
textSpan
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function definitionSpan(fn: number): protocol.TextSpan {
|
||||
return { start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } };
|
||||
}
|
||||
function importSpan(fn: number): protocol.TextSpan {
|
||||
return { start: { line: fn + 1, offset: 5 }, end: { line: fn + 1, offset: 8 } };
|
||||
}
|
||||
function usageSpan(fn: number): protocol.TextSpan {
|
||||
return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } };
|
||||
}
|
||||
|
||||
function renameFromDependencyTs(fn: number): SessionAction<protocol.RenameRequest, protocol.RenameResponseBody> {
|
||||
const triggerSpan = definitionSpan(fn);
|
||||
return {
|
||||
reqName: "rename",
|
||||
request: {
|
||||
command: protocol.CommandTypes.Rename,
|
||||
arguments: { file: dependencyTs.path, ...triggerSpan.start }
|
||||
},
|
||||
expectedResponse: {
|
||||
info: {
|
||||
canRename: true,
|
||||
fileToRename: undefined,
|
||||
displayName: `fn${fn}`,
|
||||
fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`,
|
||||
kind: ScriptElementKind.functionElement,
|
||||
kindModifiers: "export",
|
||||
triggerSpan
|
||||
},
|
||||
locs: [
|
||||
{ file: dependencyTs.path, locs: [triggerSpan] }
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function renameFromDependencyTsWithBothProjectsOpen(fn: number): SessionAction<protocol.RenameRequest, protocol.RenameResponseBody> {
|
||||
const { reqName, request, expectedResponse } = renameFromDependencyTs(fn);
|
||||
const { info, locs } = expectedResponse;
|
||||
return {
|
||||
reqName,
|
||||
request,
|
||||
expectedResponse: {
|
||||
info,
|
||||
locs: [
|
||||
locs[0],
|
||||
{
|
||||
file: mainTs.path,
|
||||
locs: [
|
||||
importSpan(fn),
|
||||
usageSpan(fn)
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// Only dependency result
|
||||
expectedResponseNoMap: expectedResponse,
|
||||
expectedResponseNoDts: expectedResponse
|
||||
};
|
||||
}
|
||||
|
||||
// Returns request and expected Response
|
||||
type SessionActionGetter<Req = protocol.Request, Response = {}> = (fn: number) => SessionAction<Req, Response>;
|
||||
// Open File, expectedProjectActualFiles, actionGetter, openFileLastLine
|
||||
interface DocumentPositionMapperVerifier {
|
||||
openFile: File;
|
||||
expectedProjectActualFiles: ReadonlyArray<string>;
|
||||
actionGetter: SessionActionGetter;
|
||||
openFileLastLine: number;
|
||||
}
|
||||
function verifyDocumentPositionMapperUpdates(
|
||||
mainScenario: string,
|
||||
verifier: ReadonlyArray<DocumentPositionMapperVerifier>,
|
||||
closedInfos: ReadonlyArray<string>) {
|
||||
|
||||
const openFiles = verifier.map(v => v.openFile);
|
||||
const expectedProjectActualFiles = verifier.map(v => v.expectedProjectActualFiles);
|
||||
const actionGetters = verifier.map(v => v.actionGetter);
|
||||
const openFileLastLines = verifier.map(v => v.openFileLastLine);
|
||||
|
||||
const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`);
|
||||
const openInfos = openFiles.map(f => f.path);
|
||||
// When usage and dependency are used, dependency config is part of closedInfo so ignore
|
||||
const otherWatchedFiles = verifier.length > 1 ? [configFiles[0]] : configFiles;
|
||||
function openTsFile(onHostCreate?: (host: TestServerHost) => void) {
|
||||
const host = createHost(files, [mainConfig.path]);
|
||||
if (onHostCreate) {
|
||||
onHostCreate(host);
|
||||
}
|
||||
const session = createSession(host);
|
||||
openFilesForSession([...openFiles, randomFile], session);
|
||||
return { host, session };
|
||||
}
|
||||
|
||||
function checkProject(session: TestSession, noDts?: true) {
|
||||
const service = session.getProjectService();
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length });
|
||||
configFiles.forEach((configFile, index) => {
|
||||
checkProjectActualFiles(
|
||||
service.configuredProjects.get(configFile)!,
|
||||
noDts ?
|
||||
expectedProjectActualFiles[index].filter(f => f.toLowerCase() !== dtsPath) :
|
||||
expectedProjectActualFiles[index]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function verifyInfos(session: TestSession, host: TestServerHost) {
|
||||
verifyInfosWithRandom(session, host, openInfos, closedInfos, otherWatchedFiles);
|
||||
}
|
||||
|
||||
function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: true) {
|
||||
const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined);
|
||||
verifyInfosWithRandom(
|
||||
session,
|
||||
host,
|
||||
openInfos,
|
||||
closedInfos.filter(f => f !== dtsMapClosedInfo && (dependencyTsOK || f !== dependencyTs.path)),
|
||||
dtsMapClosedInfo ? otherWatchedFiles.concat(dtsMapClosedInfo) : otherWatchedFiles
|
||||
);
|
||||
}
|
||||
|
||||
function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) {
|
||||
const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined);
|
||||
const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined);
|
||||
verifyInfosWithRandom(
|
||||
session,
|
||||
host,
|
||||
openInfos,
|
||||
closedInfos.filter(f => (dependencyTsAndMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsAndMapOk || f !== dependencyTs.path)),
|
||||
// When project actual file contains dts, it needs to be watched
|
||||
dtsClosedInfo && expectedProjectActualFiles.some(expectedProjectActualFiles => expectedProjectActualFiles.some(f => f.toLowerCase() === dtsPath)) ?
|
||||
otherWatchedFiles.concat(dtsClosedInfo) :
|
||||
otherWatchedFiles
|
||||
);
|
||||
}
|
||||
|
||||
function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) {
|
||||
assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap);
|
||||
if (notEqual) {
|
||||
assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper);
|
||||
}
|
||||
else {
|
||||
assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper);
|
||||
}
|
||||
}
|
||||
|
||||
function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) {
|
||||
const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = actionGetter(fn);
|
||||
const { response } = session.executeCommandSeq(request);
|
||||
return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts };
|
||||
}
|
||||
|
||||
function firstAction(session: TestSession) {
|
||||
actionGetters.forEach(actionGetter => action(actionGetter, 1, session));
|
||||
}
|
||||
|
||||
function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType<typeof action>, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) {
|
||||
// action
|
||||
let isFirst = true;
|
||||
for (const actionGetter of actionGetters) {
|
||||
for (let fn = 1; fn <= 5; fn++) {
|
||||
const result = action(actionGetter, fn, session);
|
||||
const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath);
|
||||
if (dtsAbsent) {
|
||||
assert.isUndefined(dtsInfo);
|
||||
}
|
||||
else {
|
||||
assert.isDefined(dtsInfo);
|
||||
}
|
||||
verifyAction(result, dtsInfo, isFirst);
|
||||
isFirst = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function verifyAllFnAction(
|
||||
session: TestSession,
|
||||
host: TestServerHost,
|
||||
firstDocumentPositionMapperNotEquals?: true,
|
||||
dependencyMap?: server.ScriptInfo,
|
||||
documentPositionMapper?: server.ScriptInfo["documentPositionMapper"]
|
||||
) {
|
||||
// action
|
||||
verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }, dtsInfo, isFirst) => {
|
||||
assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`);
|
||||
verifyInfos(session, host);
|
||||
assert.equal(dtsInfo!.sourceMapFilePath, dtsMapPath);
|
||||
if (isFirst) {
|
||||
if (dependencyMap) {
|
||||
verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals);
|
||||
documentPositionMapper = dependencyMap.documentPositionMapper;
|
||||
}
|
||||
else {
|
||||
dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!;
|
||||
documentPositionMapper = dependencyMap.documentPositionMapper;
|
||||
}
|
||||
}
|
||||
else {
|
||||
verifyDocumentPositionMapper(session, dependencyMap!, documentPositionMapper);
|
||||
}
|
||||
});
|
||||
return { dependencyMap: dependencyMap!, documentPositionMapper };
|
||||
}
|
||||
|
||||
function verifyAllFnActionWithNoMap(
|
||||
session: TestSession,
|
||||
host: TestServerHost,
|
||||
dependencyTsOK?: true
|
||||
) {
|
||||
let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"];
|
||||
// action
|
||||
verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoMap }, dtsInfo, isFirst) => {
|
||||
assert.deepEqual(response, expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`);
|
||||
verifyInfosWhenNoMapFile(session, host, dependencyTsOK);
|
||||
assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath));
|
||||
if (isFirst) {
|
||||
assert.isNotString(dtsInfo!.sourceMapFilePath);
|
||||
assert.isNotFalse(dtsInfo!.sourceMapFilePath);
|
||||
assert.isDefined(dtsInfo!.sourceMapFilePath);
|
||||
sourceMapFilePath = dtsInfo!.sourceMapFilePath;
|
||||
}
|
||||
else {
|
||||
assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath);
|
||||
}
|
||||
});
|
||||
return sourceMapFilePath;
|
||||
}
|
||||
|
||||
function verifyAllFnActionWithNoDts(
|
||||
session: TestSession,
|
||||
host: TestServerHost,
|
||||
dependencyTsAndMapOk?: true
|
||||
) {
|
||||
// action
|
||||
verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts }) => {
|
||||
assert.deepEqual(response, expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`);
|
||||
verifyInfosWhenNoDtsFile(session, host, dependencyTsAndMapOk);
|
||||
}, /*dtsAbsent*/ true);
|
||||
}
|
||||
|
||||
function verifyScenarioWithChangesWorker(
|
||||
change: (host: TestServerHost, session: TestSession) => void,
|
||||
afterActionDocumentPositionMapperNotEquals: true | undefined,
|
||||
timeoutBeforeAction: boolean
|
||||
) {
|
||||
const { host, session } = openTsFile();
|
||||
|
||||
// Create DocumentPositionMapper
|
||||
firstAction(session);
|
||||
const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!;
|
||||
const documentPositionMapper = dependencyMap.documentPositionMapper;
|
||||
|
||||
// change
|
||||
change(host, session);
|
||||
if (timeoutBeforeAction) {
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkProject(session);
|
||||
verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper);
|
||||
}
|
||||
|
||||
// action
|
||||
verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper);
|
||||
}
|
||||
|
||||
function verifyScenarioWithChanges(
|
||||
scenarioName: string,
|
||||
change: (host: TestServerHost, session: TestSession) => void,
|
||||
afterActionDocumentPositionMapperNotEquals?: true
|
||||
) {
|
||||
describe(scenarioName, () => {
|
||||
it("when timeout occurs before request", () => {
|
||||
verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true);
|
||||
});
|
||||
|
||||
it("when timeout does not occur before request", () => {
|
||||
verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function verifyMainScenarioAndScriptInfoCollection(session: TestSession, host: TestServerHost) {
|
||||
// Main scenario action
|
||||
const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, host);
|
||||
checkProject(session);
|
||||
verifyInfos(session, host);
|
||||
|
||||
// Collecting at this point retains dependency.d.ts and map
|
||||
closeFilesForSession([randomFile], session);
|
||||
openFilesForSession([randomFile], session);
|
||||
verifyInfos(session, host);
|
||||
verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper);
|
||||
|
||||
// Closing open file, removes dependencies too
|
||||
closeFilesForSession([...openFiles, randomFile], session);
|
||||
openFilesForSession([randomFile], session);
|
||||
verifyOnlyRandomInfos(session, host);
|
||||
}
|
||||
|
||||
function verifyMainScenarioAndScriptInfoCollectionWithNoMap(session: TestSession, host: TestServerHost, dependencyTsOKInScenario?: true) {
|
||||
// Main scenario action
|
||||
verifyAllFnActionWithNoMap(session, host, dependencyTsOKInScenario);
|
||||
|
||||
// Collecting at this point retains dependency.d.ts and map watcher
|
||||
closeFilesForSession([randomFile], session);
|
||||
openFilesForSession([randomFile], session);
|
||||
verifyInfosWhenNoMapFile(session, host);
|
||||
|
||||
// Closing open file, removes dependencies too
|
||||
closeFilesForSession([...openFiles, randomFile], session);
|
||||
openFilesForSession([randomFile], session);
|
||||
verifyOnlyRandomInfos(session, host);
|
||||
}
|
||||
|
||||
function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) {
|
||||
// Main scenario action
|
||||
verifyAllFnActionWithNoDts(session, host, dependencyTsAndMapOk);
|
||||
|
||||
// Collecting at this point retains dependency.d.ts and map watcher
|
||||
closeFilesForSession([randomFile], session);
|
||||
openFilesForSession([randomFile], session);
|
||||
verifyInfosWhenNoDtsFile(session, host);
|
||||
|
||||
// Closing open file, removes dependencies too
|
||||
closeFilesForSession([...openFiles, randomFile], session);
|
||||
openFilesForSession([randomFile], session);
|
||||
verifyOnlyRandomInfos(session, host);
|
||||
}
|
||||
|
||||
function verifyScenarioWhenFileNotPresent(
|
||||
scenarioName: string,
|
||||
fileLocation: string,
|
||||
verifyScenarioAndScriptInfoCollection: (session: TestSession, host: TestServerHost, dependencyTsOk?: true) => void,
|
||||
noDts?: true
|
||||
) {
|
||||
describe(scenarioName, () => {
|
||||
it(mainScenario, () => {
|
||||
const { host, session } = openTsFile(host => host.deleteFile(fileLocation));
|
||||
checkProject(session, noDts);
|
||||
|
||||
verifyScenarioAndScriptInfoCollection(session, host);
|
||||
});
|
||||
|
||||
it("when file is created", () => {
|
||||
let fileContents: string | undefined;
|
||||
const { host, session } = openTsFile(host => {
|
||||
fileContents = host.readFile(fileLocation);
|
||||
host.deleteFile(fileLocation);
|
||||
});
|
||||
firstAction(session);
|
||||
|
||||
host.writeFile(fileLocation, fileContents!);
|
||||
verifyMainScenarioAndScriptInfoCollection(session, host);
|
||||
});
|
||||
|
||||
it("when file is deleted", () => {
|
||||
const { host, session } = openTsFile();
|
||||
firstAction(session);
|
||||
|
||||
// The dependency file is deleted when orphan files are collected
|
||||
host.deleteFile(fileLocation);
|
||||
verifyScenarioAndScriptInfoCollection(session, host, /*dependencyTsOk*/ true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it(mainScenario, () => {
|
||||
const { host, session } = openTsFile();
|
||||
checkProject(session);
|
||||
|
||||
verifyMainScenarioAndScriptInfoCollection(session, host);
|
||||
});
|
||||
|
||||
// Edit
|
||||
verifyScenarioWithChanges(
|
||||
"when usage file changes, document position mapper doesnt change",
|
||||
(_host, session) => openFiles.forEach(
|
||||
(openFile, index) => session.executeCommandSeq<protocol.ChangeRequest>({
|
||||
command: protocol.CommandTypes.Change,
|
||||
arguments: { file: openFile.path, line: openFileLastLines[index], offset: 1, endLine: openFileLastLines[index], endOffset: 1, insertString: "const x = 10;" }
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Edit dts to add new fn
|
||||
verifyScenarioWithChanges(
|
||||
"when dependency .d.ts changes, document position mapper doesnt change",
|
||||
host => host.writeFile(
|
||||
dtsLocation,
|
||||
host.readFile(dtsLocation)!.replace(
|
||||
"//# sourceMappingURL=FnS.d.ts.map",
|
||||
`export declare function fn6(): void;
|
||||
//# sourceMappingURL=FnS.d.ts.map`
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Edit map file to represent added new line
|
||||
verifyScenarioWithChanges(
|
||||
"when dependency file's map changes",
|
||||
host => host.writeFile(
|
||||
dtsMapLocation,
|
||||
`{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}`
|
||||
),
|
||||
/*afterActionDocumentPositionMapperNotEquals*/ true
|
||||
);
|
||||
|
||||
verifyScenarioWhenFileNotPresent(
|
||||
"when map file is not present",
|
||||
dtsMapLocation,
|
||||
verifyMainScenarioAndScriptInfoCollectionWithNoMap
|
||||
);
|
||||
|
||||
verifyScenarioWhenFileNotPresent(
|
||||
"when .d.ts file is not present",
|
||||
dtsLocation,
|
||||
verifyMainScenarioAndScriptInfoCollectionWithNoDts,
|
||||
/*noDts*/ true
|
||||
);
|
||||
}
|
||||
|
||||
const usageVerifier: DocumentPositionMapperVerifier = {
|
||||
openFile: mainTs,
|
||||
expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath],
|
||||
actionGetter: gotoDefintinionFromMainTs,
|
||||
openFileLastLine: 14
|
||||
};
|
||||
describe("from project that uses dependency", () => {
|
||||
const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation];
|
||||
verifyDocumentPositionMapperUpdates(
|
||||
"can go to definition correctly",
|
||||
[usageVerifier],
|
||||
closedInfos
|
||||
);
|
||||
});
|
||||
|
||||
const definingVerifier: DocumentPositionMapperVerifier = {
|
||||
openFile: dependencyTs,
|
||||
expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path],
|
||||
actionGetter: renameFromDependencyTs,
|
||||
openFileLastLine: 6
|
||||
};
|
||||
describe("from defining project", () => {
|
||||
const closedInfos = [libFile.path, dtsLocation, dtsMapLocation];
|
||||
verifyDocumentPositionMapperUpdates(
|
||||
"rename locations from dependency",
|
||||
[definingVerifier],
|
||||
closedInfos
|
||||
);
|
||||
});
|
||||
|
||||
describe("when opening depedency and usage project", () => {
|
||||
const closedInfos = [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path];
|
||||
verifyDocumentPositionMapperUpdates(
|
||||
"goto Definition in usage and rename locations from defining project",
|
||||
[usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }],
|
||||
closedInfos
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
120
src/testRunner/unittests/tsserver/refactors.ts
Normal file
120
src/testRunner/unittests/tsserver/refactors.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: refactors", () => {
|
||||
it("use formatting options", () => {
|
||||
const file = {
|
||||
path: "/a.ts",
|
||||
content: "function f() {\n 1;\n}",
|
||||
};
|
||||
const host = createServerHost([file]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([file], session);
|
||||
|
||||
const response0 = session.executeCommandSeq<server.protocol.ConfigureRequest>({
|
||||
command: server.protocol.CommandTypes.Configure,
|
||||
arguments: {
|
||||
formatOptions: {
|
||||
indentSize: 2,
|
||||
},
|
||||
},
|
||||
}).response;
|
||||
assert.deepEqual(response0, /*expected*/ undefined);
|
||||
|
||||
const response1 = session.executeCommandSeq<server.protocol.GetEditsForRefactorRequest>({
|
||||
command: server.protocol.CommandTypes.GetEditsForRefactor,
|
||||
arguments: {
|
||||
refactor: "Extract Symbol",
|
||||
action: "function_scope_1",
|
||||
file: "/a.ts",
|
||||
startLine: 2,
|
||||
startOffset: 3,
|
||||
endLine: 2,
|
||||
endOffset: 4,
|
||||
},
|
||||
}).response;
|
||||
assert.deepEqual(response1, {
|
||||
edits: [
|
||||
{
|
||||
fileName: "/a.ts",
|
||||
textChanges: [
|
||||
{
|
||||
start: { line: 2, offset: 3 },
|
||||
end: { line: 2, offset: 5 },
|
||||
newText: "newFunction();",
|
||||
},
|
||||
{
|
||||
start: { line: 3, offset: 2 },
|
||||
end: { line: 3, offset: 2 },
|
||||
newText: "\n\nfunction newFunction() {\n 1;\n}\n",
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
renameFilename: "/a.ts",
|
||||
renameLocation: { line: 2, offset: 3 },
|
||||
});
|
||||
});
|
||||
|
||||
it("handles text changes in tsconfig.json", () => {
|
||||
const aTs = {
|
||||
path: "/a.ts",
|
||||
content: "export const a = 0;",
|
||||
};
|
||||
const tsconfig = {
|
||||
path: "/tsconfig.json",
|
||||
content: '{ "files": ["./a.ts"] }',
|
||||
};
|
||||
|
||||
const session = createSession(createServerHost([aTs, tsconfig]));
|
||||
openFilesForSession([aTs], session);
|
||||
|
||||
const response1 = session.executeCommandSeq<server.protocol.GetEditsForRefactorRequest>({
|
||||
command: server.protocol.CommandTypes.GetEditsForRefactor,
|
||||
arguments: {
|
||||
refactor: "Move to a new file",
|
||||
action: "Move to a new file",
|
||||
file: "/a.ts",
|
||||
startLine: 1,
|
||||
startOffset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 20,
|
||||
},
|
||||
}).response;
|
||||
assert.deepEqual(response1, {
|
||||
edits: [
|
||||
{
|
||||
fileName: "/a.ts",
|
||||
textChanges: [
|
||||
{
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 1, offset: 20 },
|
||||
newText: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fileName: "/tsconfig.json",
|
||||
textChanges: [
|
||||
{
|
||||
start: { line: 1, offset: 21 },
|
||||
end: { line: 1, offset: 21 },
|
||||
newText: ", \"./a.1.ts\"",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fileName: "/a.1.ts",
|
||||
textChanges: [
|
||||
{
|
||||
start: { line: 0, offset: 0 },
|
||||
end: { line: 0, offset: 0 },
|
||||
newText: "export const a = 0;\n",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
renameFilename: undefined,
|
||||
renameLocation: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
53
src/testRunner/unittests/tsserver/rename.ts
Normal file
53
src/testRunner/unittests/tsserver/rename.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: rename", () => {
|
||||
it("works with fileToRename", () => {
|
||||
const aTs: File = { path: "/a.ts", content: "export const a = 0;" };
|
||||
const bTs: File = { path: "/b.ts", content: 'import { a } from "./a";' };
|
||||
|
||||
const session = createSession(createServerHost([aTs, bTs]));
|
||||
openFilesForSession([bTs], session);
|
||||
|
||||
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";'));
|
||||
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
|
||||
info: {
|
||||
canRename: true,
|
||||
fileToRename: aTs.path,
|
||||
displayName: aTs.path,
|
||||
fullDisplayName: aTs.path,
|
||||
kind: ScriptElementKind.moduleElement,
|
||||
kindModifiers: "",
|
||||
triggerSpan: protocolTextSpanFromSubstring(bTs.content, "a", { index: 1 }),
|
||||
},
|
||||
locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }],
|
||||
});
|
||||
});
|
||||
|
||||
it("works with prefixText and suffixText", () => {
|
||||
const aTs: File = { path: "/a.ts", content: "const x = 0; const o = { x };" };
|
||||
const session = createSession(createServerHost([aTs]));
|
||||
openFilesForSession([aTs], session);
|
||||
|
||||
const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x"));
|
||||
assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
|
||||
info: {
|
||||
canRename: true,
|
||||
fileToRename: undefined,
|
||||
displayName: "x",
|
||||
fullDisplayName: "x",
|
||||
kind: ScriptElementKind.constElement,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"),
|
||||
},
|
||||
locs: [
|
||||
{
|
||||
file: aTs.path,
|
||||
locs: [
|
||||
protocolRenameSpanFromSubstring(aTs.content, "x"),
|
||||
protocolRenameSpanFromSubstring(aTs.content, "x", { index: 1 }, { prefixText: "x: " }),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace ts.projectSystem {
|
||||
return resolutionTrace;
|
||||
}
|
||||
|
||||
describe("resolutionCache:: tsserverProjectSystem extra resolution pass in lshost", () => {
|
||||
describe("tsserver:: resolutionCache:: tsserverProjectSystem extra resolution pass in lshost", () => {
|
||||
it("can load typings that are proper modules", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.js",
|
||||
@@ -46,7 +46,7 @@ namespace ts.projectSystem {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolutionCache:: tsserverProjectSystem watching @types", () => {
|
||||
describe("tsserver:: resolutionCache:: tsserverProjectSystem watching @types", () => {
|
||||
it("works correctly when typings are added or removed", () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.ts",
|
||||
@@ -92,7 +92,7 @@ namespace ts.projectSystem {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => {
|
||||
describe("tsserver:: resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => {
|
||||
it("should remove the `module not found` error", () => {
|
||||
const moduleFile = {
|
||||
path: "/a/b/moduleFile.ts",
|
||||
@@ -358,7 +358,7 @@ namespace ts.projectSystem {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => {
|
||||
describe("tsserver:: resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => {
|
||||
it("should restore the states for inferred projects", () => {
|
||||
const moduleFile = {
|
||||
path: "/a/b/moduleFile.ts",
|
||||
@@ -493,7 +493,7 @@ namespace ts.projectSystem {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolutionCache:: tsserverProjectSystem module resolution caching", () => {
|
||||
describe("tsserver:: resolutionCache:: tsserverProjectSystem module resolution caching", () => {
|
||||
const projectLocation = "/user/username/projects/myproject";
|
||||
const configFile: File = {
|
||||
path: `${projectLocation}/tsconfig.json`,
|
||||
|
||||
98
src/testRunner/unittests/tsserver/syntaxOperations.ts
Normal file
98
src/testRunner/unittests/tsserver/syntaxOperations.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: syntax operations", () => {
|
||||
function navBarFull(session: TestSession, file: File) {
|
||||
return JSON.stringify(session.executeCommandSeq<protocol.FileRequest>({
|
||||
command: protocol.CommandTypes.NavBarFull,
|
||||
arguments: { file: file.path }
|
||||
}).response);
|
||||
}
|
||||
|
||||
function openFile(session: TestSession, file: File) {
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: { file: file.path, fileContent: file.content }
|
||||
});
|
||||
}
|
||||
|
||||
it("works when file is removed and added with different content", () => {
|
||||
const projectRoot = "/user/username/projects/myproject";
|
||||
const app: File = {
|
||||
path: `${projectRoot}/app.ts`,
|
||||
content: "console.log('Hello world');"
|
||||
};
|
||||
const unitTest1: File = {
|
||||
path: `${projectRoot}/unitTest1.ts`,
|
||||
content: `import assert = require('assert');
|
||||
|
||||
describe("Test Suite 1", () => {
|
||||
it("Test A", () => {
|
||||
assert.ok(true, "This shouldn't fail");
|
||||
});
|
||||
|
||||
it("Test B", () => {
|
||||
assert.ok(1 === 1, "This shouldn't fail");
|
||||
assert.ok(false, "This should fail");
|
||||
});
|
||||
});`
|
||||
};
|
||||
const tsconfig: File = {
|
||||
path: `${projectRoot}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const files = [app, libFile, tsconfig];
|
||||
const host = createServerHost(files);
|
||||
const session = createSession(host);
|
||||
const service = session.getProjectService();
|
||||
openFile(session, app);
|
||||
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
const project = service.configuredProjects.get(tsconfig.path)!;
|
||||
const expectedFilesWithoutUnitTest1 = files.map(f => f.path);
|
||||
checkProjectActualFiles(project, expectedFilesWithoutUnitTest1);
|
||||
|
||||
host.writeFile(unitTest1.path, unitTest1.content);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
const expectedFilesWithUnitTest1 = expectedFilesWithoutUnitTest1.concat(unitTest1.path);
|
||||
checkProjectActualFiles(project, expectedFilesWithUnitTest1);
|
||||
|
||||
openFile(session, unitTest1);
|
||||
checkProjectActualFiles(project, expectedFilesWithUnitTest1);
|
||||
|
||||
const navBarResultUnitTest1 = navBarFull(session, unitTest1);
|
||||
host.deleteFile(unitTest1.path);
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
checkProjectActualFiles(project, expectedFilesWithoutUnitTest1);
|
||||
|
||||
session.executeCommandSeq<protocol.CloseRequest>({
|
||||
command: protocol.CommandTypes.Close,
|
||||
arguments: { file: unitTest1.path }
|
||||
});
|
||||
checkProjectActualFiles(project, expectedFilesWithoutUnitTest1);
|
||||
|
||||
const unitTest1WithChangedContent: File = {
|
||||
path: unitTest1.path,
|
||||
content: `import assert = require('assert');
|
||||
|
||||
export function Test1() {
|
||||
assert.ok(true, "This shouldn't fail");
|
||||
};
|
||||
|
||||
export function Test2() {
|
||||
assert.ok(1 === 1, "This shouldn't fail");
|
||||
assert.ok(false, "This should fail");
|
||||
};`
|
||||
};
|
||||
host.writeFile(unitTest1.path, unitTest1WithChangedContent.content);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkProjectActualFiles(project, expectedFilesWithUnitTest1);
|
||||
|
||||
openFile(session, unitTest1WithChangedContent);
|
||||
checkProjectActualFiles(project, expectedFilesWithUnitTest1);
|
||||
const sourceFile = project.getLanguageService().getNonBoundSourceFile(unitTest1WithChangedContent.path);
|
||||
assert.strictEqual(sourceFile.text, unitTest1WithChangedContent.content);
|
||||
|
||||
const navBarResultUnitTest1WithChangedContent = navBarFull(session, unitTest1WithChangedContent);
|
||||
assert.notStrictEqual(navBarResultUnitTest1WithChangedContent, navBarResultUnitTest1, "With changes in contents of unitTest file, we should see changed naviagation bar item result");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("project telemetry", () => {
|
||||
describe("tsserver:: project telemetry", () => {
|
||||
it("does nothing for inferred project", () => {
|
||||
const file = makeFile("/a.js");
|
||||
const et = new TestServerEventManager([file]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace ts.textStorage {
|
||||
describe("Text storage", () => {
|
||||
describe("tsserver:: Text storage", () => {
|
||||
const f = {
|
||||
path: "/a/app.ts",
|
||||
content: `
|
||||
|
||||
52
src/testRunner/unittests/tsserver/typeAquisition.ts
Normal file
52
src/testRunner/unittests/tsserver/typeAquisition.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: autoDiscovery", () => {
|
||||
it("does not depend on extension", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.html",
|
||||
content: ""
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/app.d.ts",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([file1, file2]);
|
||||
const projectService = createProjectService(host);
|
||||
projectService.openExternalProject({
|
||||
projectFileName: "/a/b/proj.csproj",
|
||||
rootFiles: [toExternalFile(file2.path), { fileName: file1.path, hasMixedContent: true, scriptKind: ScriptKind.JS }],
|
||||
options: {}
|
||||
});
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
const typeAcquisition = projectService.externalProjects[0].getTypeAcquisition();
|
||||
assert.isTrue(typeAcquisition.enable, "Typine acquisition should be enabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe("tsserver:: prefer typings to js", () => {
|
||||
it("during second resolution pass", () => {
|
||||
const typingsCacheLocation = "/a/typings";
|
||||
const f1 = {
|
||||
path: "/a/b/app.js",
|
||||
content: "var x = require('bar')"
|
||||
};
|
||||
const barjs = {
|
||||
path: "/a/b/node_modules/bar/index.js",
|
||||
content: "export let x = 1"
|
||||
};
|
||||
const barTypings = {
|
||||
path: `${typingsCacheLocation}/node_modules/@types/bar/index.d.ts`,
|
||||
content: "export let y: number"
|
||||
};
|
||||
const config = {
|
||||
path: "/a/b/jsconfig.json",
|
||||
content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: ["node_modules"] })
|
||||
};
|
||||
const host = createServerHost([f1, barjs, barTypings, config]);
|
||||
const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller(typingsCacheLocation, /*throttleLimit*/ 5, host) });
|
||||
|
||||
projectService.openClientFile(f1.path);
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, barTypings.path, config.path]);
|
||||
});
|
||||
});
|
||||
}
|
||||
87
src/testRunner/unittests/tsserver/typeReferenceDirectives.ts
Normal file
87
src/testRunner/unittests/tsserver/typeReferenceDirectives.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: typeReferenceDirectives", () => {
|
||||
it("when typeReferenceDirective contains UpperCasePackage", () => {
|
||||
const projectLocation = "/user/username/projects/myproject";
|
||||
const libProjectLocation = `${projectLocation}/lib`;
|
||||
const typeLib: File = {
|
||||
path: `${libProjectLocation}/@types/UpperCasePackage/index.d.ts`,
|
||||
content: `declare class BrokenTest {
|
||||
constructor(name: string, width: number, height: number, onSelect: Function);
|
||||
Name: string;
|
||||
SelectedFile: string;
|
||||
}`
|
||||
};
|
||||
const appLib: File = {
|
||||
path: `${libProjectLocation}/@app/lib/index.d.ts`,
|
||||
content: `/// <reference types="UpperCasePackage" />
|
||||
declare class TestLib {
|
||||
issue: BrokenTest;
|
||||
constructor();
|
||||
test(): void;
|
||||
}`
|
||||
};
|
||||
const testProjectLocation = `${projectLocation}/test`;
|
||||
const testFile: File = {
|
||||
path: `${testProjectLocation}/test.ts`,
|
||||
content: `class TestClass1 {
|
||||
|
||||
constructor() {
|
||||
var l = new TestLib();
|
||||
|
||||
}
|
||||
|
||||
public test2() {
|
||||
var x = new BrokenTest('',0,0,null);
|
||||
|
||||
}
|
||||
}`
|
||||
};
|
||||
const testConfig: File = {
|
||||
path: `${testProjectLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
module: "amd",
|
||||
typeRoots: ["../lib/@types", "../lib/@app"]
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const files = [typeLib, appLib, testFile, testConfig, libFile];
|
||||
const host = createServerHost(files);
|
||||
const service = createProjectService(host);
|
||||
service.openClientFile(testFile.path);
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
const project = service.configuredProjects.get(testConfig.path)!;
|
||||
checkProjectActualFiles(project, files.map(f => f.path));
|
||||
host.writeFile(appLib.path, appLib.content.replace("test()", "test2()"));
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
});
|
||||
|
||||
it("when typeReferenceDirective is relative path and in a sibling folder", () => {
|
||||
const projectRootPath = "/user/username/projects/browser-addon";
|
||||
const projectPath = `${projectRootPath}/background`;
|
||||
const file: File = {
|
||||
path: `${projectPath}/a.ts`,
|
||||
content: "let x = 10;"
|
||||
};
|
||||
const tsconfig: File = {
|
||||
path: `${projectPath}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
types: [
|
||||
"../typedefs/filesystem"
|
||||
]
|
||||
}
|
||||
})
|
||||
};
|
||||
const filesystem: File = {
|
||||
path: `${projectRootPath}/typedefs/filesystem.d.ts`,
|
||||
content: `interface LocalFileSystem { someProperty: string; }`
|
||||
};
|
||||
const files = [file, tsconfig, filesystem, libFile];
|
||||
const host = createServerHost(files);
|
||||
const service = createProjectService(host);
|
||||
service.openClientFile(file.path);
|
||||
});
|
||||
});
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
45
src/testRunner/unittests/tsserver/untitledFiles.ts
Normal file
45
src/testRunner/unittests/tsserver/untitledFiles.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("tsserver:: Untitled files", () => {
|
||||
it("Can convert positions to locations", () => {
|
||||
const aTs: File = { path: "/proj/a.ts", content: "" };
|
||||
const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" };
|
||||
const session = createSession(createServerHost([aTs, tsconfig]));
|
||||
|
||||
openFilesForSession([aTs], session);
|
||||
|
||||
const untitledFile = "untitled:^Untitled-1";
|
||||
executeSessionRequestNoResponse<protocol.OpenRequest>(session, protocol.CommandTypes.Open, {
|
||||
file: untitledFile,
|
||||
fileContent: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />\nlet foo = 1;\nfooo/**/`,
|
||||
scriptKindName: "TS",
|
||||
projectRootPath: "/proj",
|
||||
});
|
||||
|
||||
const response = executeSessionRequest<protocol.CodeFixRequest, protocol.CodeFixResponse>(session, protocol.CommandTypes.GetCodeFixes, {
|
||||
file: untitledFile,
|
||||
startLine: 3,
|
||||
startOffset: 1,
|
||||
endLine: 3,
|
||||
endOffset: 5,
|
||||
errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code],
|
||||
});
|
||||
assert.deepEqual<ReadonlyArray<protocol.CodeFixAction> | undefined>(response, [
|
||||
{
|
||||
description: "Change spelling to 'foo'",
|
||||
fixAllDescription: "Fix all detected spelling errors",
|
||||
fixId: "fixSpelling",
|
||||
fixName: "spelling",
|
||||
changes: [{
|
||||
fileName: untitledFile,
|
||||
textChanges: [{
|
||||
start: { line: 3, offset: 1 },
|
||||
end: { line: 3, offset: 5 },
|
||||
newText: "foo",
|
||||
}],
|
||||
}],
|
||||
commands: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace ts {
|
||||
assert.equal(editedText, checkText);
|
||||
}
|
||||
|
||||
describe(`VersionCache TS code`, () => {
|
||||
describe(`tsserver:: VersionCache TS code`, () => {
|
||||
let validateEditAtLineCharIndex: (line: number, char: number, deleteLength: number, insertString: string) => void;
|
||||
|
||||
before(() => {
|
||||
@@ -77,7 +77,7 @@ var q:Point=<Point>p;`;
|
||||
});
|
||||
});
|
||||
|
||||
describe(`VersionCache simple text`, () => {
|
||||
describe(`tsserver:: VersionCache simple text`, () => {
|
||||
let validateEditAtPosition: (position: number, deleteLength: number, insertString: string) => void;
|
||||
let testContent: string;
|
||||
let lines: string[];
|
||||
@@ -181,7 +181,7 @@ and grew 1cm per day`;
|
||||
});
|
||||
});
|
||||
|
||||
describe(`VersionCache stress test`, () => {
|
||||
describe(`tsserver:: VersionCache stress test`, () => {
|
||||
let rsa: number[] = [];
|
||||
let la: number[] = [];
|
||||
let las: number[] = [];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ts.projectSystem {
|
||||
import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
|
||||
describe("watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => {
|
||||
describe("tsserver:: watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => {
|
||||
function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) {
|
||||
const projectFolder = "/a/username/project";
|
||||
const projectSrcFolder = `${projectFolder}/src`;
|
||||
@@ -83,7 +83,7 @@ namespace ts.projectSystem {
|
||||
});
|
||||
});
|
||||
|
||||
describe("watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => {
|
||||
describe("tsserver:: watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => {
|
||||
function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) {
|
||||
const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`;
|
||||
const configFile: File = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user