Merge pull request #32028 from microsoft/referencesPrototypeSourceFile
For editing experience, use source instead of .d.ts files from project references
This commit is contained in:
commit
992c211c22
|
@ -425,7 +425,7 @@ namespace ts {
|
|||
const options = program.getCompilerOptions();
|
||||
forEach(program.getSourceFiles(), f =>
|
||||
program.isSourceFileDefaultLibrary(f) &&
|
||||
!skipTypeChecking(f, options) &&
|
||||
!skipTypeChecking(f, options, program) &&
|
||||
removeSemanticDiagnosticsOf(state, f.path)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -572,7 +572,7 @@ namespace ts {
|
|||
return node && getTypeArgumentConstraint(node);
|
||||
},
|
||||
getSuggestionDiagnostics: (file, ct) => {
|
||||
if (skipTypeChecking(file, compilerOptions)) {
|
||||
if (skipTypeChecking(file, compilerOptions, host)) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
|
@ -31048,7 +31048,7 @@ namespace ts {
|
|||
function checkSourceFileWorker(node: SourceFile) {
|
||||
const links = getNodeLinks(node);
|
||||
if (!(links.flags & NodeCheckFlags.TypeChecked)) {
|
||||
if (skipTypeChecking(node, compilerOptions)) {
|
||||
if (skipTypeChecking(node, compilerOptions, host)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -772,6 +772,12 @@ namespace ts {
|
|||
category: Diagnostics.Advanced_Options,
|
||||
description: Diagnostics.Disable_size_limitations_on_JavaScript_projects
|
||||
},
|
||||
{
|
||||
name: "disableSourceOfProjectReferenceRedirect",
|
||||
type: "boolean",
|
||||
category: Diagnostics.Advanced_Options,
|
||||
description: Diagnostics.Disable_use_of_source_files_instead_of_declaration_files_from_referenced_projects
|
||||
},
|
||||
{
|
||||
name: "noImplicitUseStrict",
|
||||
type: "boolean",
|
||||
|
|
|
@ -4036,6 +4036,10 @@
|
|||
"category": "Message",
|
||||
"code": 6220
|
||||
},
|
||||
"Disable use of source files instead of declaration files from referenced projects.": {
|
||||
"category": "Message",
|
||||
"code": 6221
|
||||
},
|
||||
|
||||
"Projects to reference": {
|
||||
"category": "Message",
|
||||
|
|
|
@ -817,6 +817,8 @@ namespace ts {
|
|||
let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined;
|
||||
let projectReferenceRedirects: Map<ResolvedProjectReference | false> | undefined;
|
||||
let mapFromFileToProjectReferenceRedirects: Map<Path> | undefined;
|
||||
let mapFromToProjectReferenceRedirectSource: Map<SourceOfProjectReferenceRedirect> | undefined;
|
||||
const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect && host.useSourceOfProjectReferenceRedirect();
|
||||
|
||||
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
||||
// We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks
|
||||
|
@ -831,10 +833,24 @@ namespace ts {
|
|||
if (!resolvedProjectReferences) {
|
||||
resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile);
|
||||
}
|
||||
if (host.setResolvedProjectReferenceCallbacks) {
|
||||
host.setResolvedProjectReferenceCallbacks({
|
||||
getSourceOfProjectReferenceRedirect,
|
||||
forEachResolvedProjectReference
|
||||
});
|
||||
}
|
||||
if (rootNames.length) {
|
||||
for (const parsedRef of resolvedProjectReferences) {
|
||||
if (!parsedRef) continue;
|
||||
const out = parsedRef.commandLine.options.outFile || parsedRef.commandLine.options.out;
|
||||
if (useSourceOfProjectReferenceRedirect) {
|
||||
if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
|
||||
for (const fileName of parsedRef.commandLine.fileNames) {
|
||||
processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (out) {
|
||||
processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
|
||||
}
|
||||
|
@ -848,6 +864,7 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false));
|
||||
|
||||
|
@ -955,6 +972,7 @@ namespace ts {
|
|||
getResolvedProjectReferenceToRedirect,
|
||||
getResolvedProjectReferenceByPath,
|
||||
forEachResolvedProjectReference,
|
||||
isSourceOfProjectReferenceRedirect,
|
||||
emitBuildInfo
|
||||
};
|
||||
|
||||
|
@ -987,9 +1005,15 @@ namespace ts {
|
|||
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
}
|
||||
|
||||
function isValidSourceFileForEmit(file: SourceFile) {
|
||||
// source file is allowed to be emitted and its not source of project reference redirect
|
||||
return sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect) &&
|
||||
!isSourceOfProjectReferenceRedirect(file.fileName);
|
||||
}
|
||||
|
||||
function getCommonSourceDirectory() {
|
||||
if (commonSourceDirectory === undefined) {
|
||||
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
|
||||
const emittedFiles = filter(files, file => isValidSourceFileForEmit(file));
|
||||
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
|
||||
// If a rootDir is specified use it as the commonSourceDirectory
|
||||
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
|
||||
|
@ -1220,6 +1244,12 @@ namespace ts {
|
|||
}
|
||||
if (projectReferences) {
|
||||
resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile);
|
||||
if (host.setResolvedProjectReferenceCallbacks) {
|
||||
host.setResolvedProjectReferenceCallbacks({
|
||||
getSourceOfProjectReferenceRedirect,
|
||||
forEachResolvedProjectReference
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// check if program source files has changed in the way that can affect structure of the program
|
||||
|
@ -1403,6 +1433,13 @@ namespace ts {
|
|||
for (const newSourceFile of newSourceFiles) {
|
||||
const filePath = newSourceFile.path;
|
||||
addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath);
|
||||
if (useSourceOfProjectReferenceRedirect) {
|
||||
const redirectProject = getProjectReferenceRedirectProject(newSourceFile.fileName);
|
||||
if (redirectProject && !(redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out)) {
|
||||
const redirect = getProjectReferenceOutputName(redirectProject, newSourceFile.fileName);
|
||||
addFileToFilesByName(newSourceFile, toPath(redirect), /*redirectedPath*/ undefined);
|
||||
}
|
||||
}
|
||||
// Set the file as found during node modules search if it was found that way in old progra,
|
||||
if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(newSourceFile.resolvedPath)!)) {
|
||||
sourceFilesFoundSearchingNodeModules.set(filePath, true);
|
||||
|
@ -1682,7 +1719,7 @@ namespace ts {
|
|||
|
||||
function getSemanticDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] | undefined {
|
||||
return runWithCancellationToken(() => {
|
||||
if (skipTypeChecking(sourceFile, options)) {
|
||||
if (skipTypeChecking(sourceFile, options, program)) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
|
@ -2234,6 +2271,16 @@ namespace ts {
|
|||
|
||||
// Get source file from normalized fileName
|
||||
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: RefFile | undefined, packageId: PackageId | undefined): SourceFile | undefined {
|
||||
if (useSourceOfProjectReferenceRedirect) {
|
||||
const source = getSourceOfProjectReferenceRedirect(fileName);
|
||||
if (source) {
|
||||
const file = isString(source) ?
|
||||
findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, refFile, packageId) :
|
||||
undefined;
|
||||
if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
const originalFileName = fileName;
|
||||
if (filesByName.has(path)) {
|
||||
const file = filesByName.get(path);
|
||||
|
@ -2282,7 +2329,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
let redirectedPath: Path | undefined;
|
||||
if (refFile) {
|
||||
if (refFile && !useSourceOfProjectReferenceRedirect) {
|
||||
const redirectProject = getProjectReferenceRedirectProject(fileName);
|
||||
if (redirectProject) {
|
||||
if (redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out) {
|
||||
|
@ -2451,6 +2498,36 @@ namespace ts {
|
|||
});
|
||||
}
|
||||
|
||||
function getSourceOfProjectReferenceRedirect(file: string) {
|
||||
if (!isDeclarationFileName(file)) return undefined;
|
||||
if (mapFromToProjectReferenceRedirectSource === undefined) {
|
||||
mapFromToProjectReferenceRedirectSource = createMap();
|
||||
forEachResolvedProjectReference(resolvedRef => {
|
||||
if (resolvedRef) {
|
||||
const out = resolvedRef.commandLine.options.outFile || resolvedRef.commandLine.options.out;
|
||||
if (out) {
|
||||
// Dont know which source file it means so return true?
|
||||
const outputDts = changeExtension(out, Extension.Dts);
|
||||
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true);
|
||||
}
|
||||
else {
|
||||
forEach(resolvedRef.commandLine.fileNames, fileName => {
|
||||
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
|
||||
const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames());
|
||||
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return mapFromToProjectReferenceRedirectSource.get(toPath(file));
|
||||
}
|
||||
|
||||
function isSourceOfProjectReferenceRedirect(fileName: string) {
|
||||
return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName);
|
||||
}
|
||||
|
||||
function forEachProjectReference<T>(
|
||||
projectReferences: readonly ProjectReference[] | undefined,
|
||||
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
|
||||
|
@ -2858,8 +2935,7 @@ namespace ts {
|
|||
const rootPaths = arrayToSet(rootNames, toPath);
|
||||
for (const file of files) {
|
||||
// Ignore file that is not emitted
|
||||
if (!sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect)) continue;
|
||||
if (!rootPaths.has(file.path)) {
|
||||
if (isValidSourceFileForEmit(file) && !rootPaths.has(file.path)) {
|
||||
addProgramDiagnosticAtRefPath(
|
||||
file,
|
||||
rootPaths,
|
||||
|
|
|
@ -522,6 +522,33 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function recursiveCreateDirectory(directoryPath: string, sys: System) {
|
||||
const basePath = getDirectoryPath(directoryPath);
|
||||
const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath);
|
||||
if (shouldCreateParent) {
|
||||
recursiveCreateDirectory(basePath, sys);
|
||||
}
|
||||
if (shouldCreateParent || !sys.directoryExists(directoryPath)) {
|
||||
sys.createDirectory(directoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* patch writefile to create folder before writing the file
|
||||
*/
|
||||
/*@internal*/
|
||||
export function patchWriteFileEnsuringDirectory(sys: System) {
|
||||
// patch writefile to create folder before writing the file
|
||||
const originalWriteFile = sys.writeFile;
|
||||
sys.writeFile = (path, data, writeBom) => {
|
||||
const directoryPath = getDirectoryPath(normalizeSlashes(path));
|
||||
if (directoryPath && !sys.directoryExists(directoryPath)) {
|
||||
recursiveCreateDirectory(directoryPath, sys);
|
||||
}
|
||||
originalWriteFile.call(sys, path, data, writeBom);
|
||||
};
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex";
|
||||
|
||||
|
@ -1365,17 +1392,6 @@ namespace ts {
|
|||
};
|
||||
}
|
||||
|
||||
function recursiveCreateDirectory(directoryPath: string, sys: System) {
|
||||
const basePath = getDirectoryPath(directoryPath);
|
||||
const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath);
|
||||
if (shouldCreateParent) {
|
||||
recursiveCreateDirectory(basePath, sys);
|
||||
}
|
||||
if (shouldCreateParent || !sys.directoryExists(directoryPath)) {
|
||||
sys.createDirectory(directoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
let sys: System | undefined;
|
||||
if (typeof ChakraHost !== "undefined") {
|
||||
sys = getChakraSystem();
|
||||
|
@ -1387,14 +1403,7 @@ namespace ts {
|
|||
}
|
||||
if (sys) {
|
||||
// patch writefile to create folder before writing the file
|
||||
const originalWriteFile = sys.writeFile;
|
||||
sys.writeFile = (path, data, writeBom) => {
|
||||
const directoryPath = getDirectoryPath(normalizeSlashes(path));
|
||||
if (directoryPath && !sys!.directoryExists(directoryPath)) {
|
||||
recursiveCreateDirectory(directoryPath, sys!);
|
||||
}
|
||||
originalWriteFile.call(sys, path, data, writeBom);
|
||||
};
|
||||
patchWriteFileEnsuringDirectory(sys);
|
||||
}
|
||||
return sys!;
|
||||
})();
|
||||
|
|
|
@ -3065,6 +3065,7 @@ namespace ts {
|
|||
/*@internal*/ getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
|
||||
/*@internal*/ forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
|
||||
/*@internal*/ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined;
|
||||
/*@internal*/ isSourceOfProjectReferenceRedirect(fileName: string): boolean;
|
||||
/*@internal*/ getProgramBuildInfo?(): ProgramBuildInfo | undefined;
|
||||
/*@internal*/ emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
|
||||
}
|
||||
|
@ -3165,6 +3166,7 @@ namespace ts {
|
|||
getSourceFile(fileName: string): SourceFile | undefined;
|
||||
getResolvedTypeReferenceDirectives(): ReadonlyMap<ResolvedTypeReferenceDirective | undefined>;
|
||||
getProjectReferenceRedirect(fileName: string): string | undefined;
|
||||
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
|
||||
|
||||
readonly redirectTargetsMap: RedirectTargetsMap;
|
||||
}
|
||||
|
@ -4775,6 +4777,7 @@ namespace ts {
|
|||
/* @internal */ diagnostics?: boolean;
|
||||
/* @internal */ extendedDiagnostics?: boolean;
|
||||
disableSizeLimit?: boolean;
|
||||
disableSourceOfProjectReferenceRedirect?: boolean;
|
||||
downlevelIteration?: boolean;
|
||||
emitBOM?: boolean;
|
||||
emitDecoratorMetadata?: boolean;
|
||||
|
@ -5303,11 +5306,23 @@ namespace ts {
|
|||
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
|
||||
createHash?(data: string): string;
|
||||
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
|
||||
/* @internal */ setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void;
|
||||
/* @internal */ useSourceOfProjectReferenceRedirect?(): boolean;
|
||||
|
||||
// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base
|
||||
/*@internal*/createDirectory?(directory: string): void;
|
||||
}
|
||||
|
||||
/** true if --out otherwise source file name */
|
||||
/*@internal*/
|
||||
export type SourceOfProjectReferenceRedirect = string | true;
|
||||
|
||||
/*@internal*/
|
||||
export interface ResolvedProjectReferenceCallbacks {
|
||||
getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined;
|
||||
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum TransformFlags {
|
||||
None = 0,
|
||||
|
|
|
@ -8713,11 +8713,16 @@ namespace ts {
|
|||
return { pos: typeParameters.pos - 1, end: typeParameters.end + 1 };
|
||||
}
|
||||
|
||||
export function skipTypeChecking(sourceFile: SourceFile, options: CompilerOptions) {
|
||||
export interface HostWithIsSourceOfProjectReferenceRedirect {
|
||||
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
|
||||
}
|
||||
export function skipTypeChecking(sourceFile: SourceFile, options: CompilerOptions, host: HostWithIsSourceOfProjectReferenceRedirect) {
|
||||
// If skipLibCheck is enabled, skip reporting errors if file is a declaration file.
|
||||
// If skipDefaultLibCheck is enabled, skip reporting errors if file contains a
|
||||
// '/// <reference no-default-lib="true"/>' directive.
|
||||
return options.skipLibCheck && sourceFile.isDeclarationFile || options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib;
|
||||
return (options.skipLibCheck && sourceFile.isDeclarationFile ||
|
||||
options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib) ||
|
||||
host.isSourceOfProjectReferenceRedirect(sourceFile.fileName);
|
||||
}
|
||||
|
||||
export function isJsonEqual(a: unknown, b: unknown): boolean {
|
||||
|
|
|
@ -44,7 +44,10 @@ interface Array<T> { length: number; [n: number]: T; }`
|
|||
}
|
||||
|
||||
export function createServerHost(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost {
|
||||
return new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params);
|
||||
const host = new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params);
|
||||
// Just like sys, patch the host to use writeFile
|
||||
patchWriteFileEnsuringDirectory(host);
|
||||
return host;
|
||||
}
|
||||
|
||||
export interface File {
|
||||
|
@ -174,8 +177,8 @@ interface Array<T> { length: number; [n: number]: T; }`
|
|||
}
|
||||
}
|
||||
|
||||
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) {
|
||||
checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles);
|
||||
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[], additionalInfo?: string) {
|
||||
checkMapKeys(`watchedFiles:: ${additionalInfo || ""}::`, host.watchedFiles, expectedFiles);
|
||||
}
|
||||
|
||||
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyMap<number>): void;
|
||||
|
@ -1016,6 +1019,19 @@ interface Array<T> { length: number; [n: number]: T; }`
|
|||
}
|
||||
}
|
||||
|
||||
export type TestServerHostTrackingWrittenFiles = TestServerHost & { writtenFiles: Map<true>; };
|
||||
|
||||
export function changeToHostTrackingWrittenFiles(inputHost: TestServerHost) {
|
||||
const host = inputHost as TestServerHostTrackingWrittenFiles;
|
||||
const originalWriteFile = host.writeFile;
|
||||
host.writtenFiles = createMap<true>();
|
||||
host.writeFile = (fileName, content) => {
|
||||
originalWriteFile.call(host, fileName, content);
|
||||
const path = host.toFullPath(fileName);
|
||||
host.writtenFiles.set(path, true);
|
||||
};
|
||||
return host;
|
||||
}
|
||||
export const tsbuildProjectsLocation = "/user/username/projects";
|
||||
export function getTsBuildProjectFilePath(project: string, file: string) {
|
||||
return `${tsbuildProjectsLocation}/${project}/${file}`;
|
||||
|
|
|
@ -1777,6 +1777,12 @@ namespace ts.server {
|
|||
configFileErrors.push(...parsedCommandLine.errors);
|
||||
}
|
||||
|
||||
this.logger.info(`Config: ${configFilename} : ${JSON.stringify({
|
||||
rootNames: parsedCommandLine.fileNames,
|
||||
options: parsedCommandLine.options,
|
||||
projectReferences: parsedCommandLine.projectReferences
|
||||
}, /*replacer*/ undefined, " ")}`);
|
||||
|
||||
Debug.assert(!!parsedCommandLine.fileNames);
|
||||
const compilerOptions = parsedCommandLine.options;
|
||||
|
||||
|
@ -1818,7 +1824,7 @@ namespace ts.server {
|
|||
let scriptInfo: ScriptInfo | NormalizedPath;
|
||||
let path: Path;
|
||||
// Use the project's fileExists so that it can use caching instead of reaching to disk for the query
|
||||
if (!isDynamic && !project.fileExists(newRootFile)) {
|
||||
if (!isDynamic && !project.fileExistsWithCache(newRootFile)) {
|
||||
path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName);
|
||||
const existingValue = projectRootFilesMap.get(path)!;
|
||||
if (isScriptInfo(existingValue)) {
|
||||
|
@ -1851,7 +1857,7 @@ namespace ts.server {
|
|||
projectRootFilesMap.forEach((value, path) => {
|
||||
if (!newRootScriptInfoMap.has(path)) {
|
||||
if (isScriptInfo(value)) {
|
||||
project.removeFile(value, project.fileExists(path), /*detachFromProject*/ true);
|
||||
project.removeFile(value, project.fileExistsWithCache(path), /*detachFromProject*/ true);
|
||||
}
|
||||
else {
|
||||
projectRootFilesMap.delete(path);
|
||||
|
@ -2584,7 +2590,9 @@ namespace ts.server {
|
|||
|
||||
/*@internal*/
|
||||
getOriginalLocationEnsuringConfiguredProject(project: Project, location: DocumentPosition): DocumentPosition | undefined {
|
||||
const originalLocation = project.getSourceMapper().tryGetSourcePosition(location);
|
||||
const originalLocation = project.isSourceOfProjectReferenceRedirect(location.fileName) ?
|
||||
location :
|
||||
project.getSourceMapper().tryGetSourcePosition(location);
|
||||
if (!originalLocation) return undefined;
|
||||
|
||||
const { fileName } = originalLocation;
|
||||
|
@ -2595,7 +2603,8 @@ namespace ts.server {
|
|||
if (!configFileName) return undefined;
|
||||
|
||||
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) ||
|
||||
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName} for location: ${location.fileName}`);
|
||||
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`);
|
||||
if (configuredProject === project) return originalLocation;
|
||||
updateProjectIfDirty(configuredProject);
|
||||
// Keep this configured project as referenced from project
|
||||
addOriginalConfiguredProject(configuredProject);
|
||||
|
|
|
@ -196,6 +196,11 @@ namespace ts.server {
|
|||
/*@internal*/
|
||||
originalConfiguredProjects: Map<true> | undefined;
|
||||
|
||||
/*@internal*/
|
||||
getResolvedProjectReferenceToRedirect(_fileName: string): ResolvedProjectReference | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private readonly cancellationToken: ThrottledCancellationToken;
|
||||
|
||||
public isNonTsProject() {
|
||||
|
@ -391,6 +396,11 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
fileExists(file: string): boolean {
|
||||
return this.fileExistsWithCache(file);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
fileExistsWithCache(file: string): boolean {
|
||||
// As an optimization, don't hit the disks for files we already know don't exist
|
||||
// (because we're watching for their creation).
|
||||
const path = this.toPath(file);
|
||||
|
@ -527,8 +537,11 @@ namespace ts.server {
|
|||
return this.projectService.getSourceFileLike(fileName, this);
|
||||
}
|
||||
|
||||
private shouldEmitFile(scriptInfo: ScriptInfo) {
|
||||
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
|
||||
/*@internal*/
|
||||
shouldEmitFile(scriptInfo: ScriptInfo | undefined) {
|
||||
return scriptInfo &&
|
||||
!scriptInfo.isDynamicOrHasMixedContent() &&
|
||||
!this.program!.isSourceOfProjectReferenceRedirect(scriptInfo.path);
|
||||
}
|
||||
|
||||
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
|
||||
|
@ -538,7 +551,7 @@ namespace ts.server {
|
|||
updateProjectIfDirty(this);
|
||||
this.builderState = BuilderState.create(this.program!, this.projectService.toCanonicalFileName, this.builderState);
|
||||
return mapDefined(BuilderState.getFilesAffectedBy(this.builderState, this.program!, scriptInfo.path, this.cancellationToken, data => this.projectService.host.createHash!(data)), // TODO: GH#18217
|
||||
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)!) ? sourceFile.fileName : undefined);
|
||||
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1223,6 +1236,11 @@ namespace ts.server {
|
|||
this.rootFilesMap.delete(info.path);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
isSourceOfProjectReferenceRedirect(fileName: string) {
|
||||
return !!this.program && this.program.isSourceOfProjectReferenceRedirect(fileName);
|
||||
}
|
||||
|
||||
protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
|
||||
const host = this.projectService.host;
|
||||
|
||||
|
@ -1475,6 +1493,8 @@ namespace ts.server {
|
|||
configFileWatcher: FileWatcher | undefined;
|
||||
private directoriesWatchedForWildcards: Map<WildcardDirectoryWatcher> | undefined;
|
||||
readonly canonicalConfigFilePath: NormalizedPath;
|
||||
private projectReferenceCallbacks: ResolvedProjectReferenceCallbacks | undefined;
|
||||
private mapOfDeclarationDirectories: Map<true> | undefined;
|
||||
|
||||
/* @internal */
|
||||
pendingReload: ConfigFileProgramReloadLevel | undefined;
|
||||
|
@ -1520,6 +1540,63 @@ namespace ts.server {
|
|||
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
setResolvedProjectReferenceCallbacks(projectReferenceCallbacks: ResolvedProjectReferenceCallbacks) {
|
||||
this.projectReferenceCallbacks = projectReferenceCallbacks;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
useSourceOfProjectReferenceRedirect = () => !!this.languageServiceEnabled &&
|
||||
!this.getCompilerOptions().disableSourceOfProjectReferenceRedirect;
|
||||
|
||||
/**
|
||||
* This implementation of fileExists checks if the file being requested is
|
||||
* .d.ts file for the referenced Project.
|
||||
* If it is it returns true irrespective of whether that file exists on host
|
||||
*/
|
||||
fileExists(file: string): boolean {
|
||||
// Project references go to source file instead of .d.ts file
|
||||
if (this.useSourceOfProjectReferenceRedirect() && this.projectReferenceCallbacks) {
|
||||
const source = this.projectReferenceCallbacks.getSourceOfProjectReferenceRedirect(file);
|
||||
if (source) return isString(source) ? super.fileExists(source) : true;
|
||||
}
|
||||
return super.fileExists(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation of directoryExists checks if the directory being requested is
|
||||
* directory of .d.ts file for the referenced Project.
|
||||
* If it is it returns true irrespective of whether that directory exists on host
|
||||
*/
|
||||
directoryExists(path: string): boolean {
|
||||
if (super.directoryExists(path)) return true;
|
||||
if (!this.useSourceOfProjectReferenceRedirect() || !this.projectReferenceCallbacks) return false;
|
||||
|
||||
if (!this.mapOfDeclarationDirectories) {
|
||||
this.mapOfDeclarationDirectories = createMap();
|
||||
this.projectReferenceCallbacks.forEachResolvedProjectReference(ref => {
|
||||
if (!ref) return;
|
||||
const out = ref.commandLine.options.outFile || ref.commandLine.options.outDir;
|
||||
if (out) {
|
||||
this.mapOfDeclarationDirectories!.set(getDirectoryPath(this.toPath(out)), true);
|
||||
}
|
||||
else {
|
||||
// Set declaration's in different locations only, if they are next to source the directory present doesnt change
|
||||
const declarationDir = ref.commandLine.options.declarationDir || ref.commandLine.options.outDir;
|
||||
if (declarationDir) {
|
||||
this.mapOfDeclarationDirectories!.set(this.toPath(declarationDir), true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const dirPath = this.toPath(path);
|
||||
const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`;
|
||||
return !!forEachKey(
|
||||
this.mapOfDeclarationDirectories,
|
||||
declDirPath => dirPath === declDirPath || startsWith(declDirPath, dirPathWithTrailingDirectorySeparator)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
|
||||
* @returns: true if set of files in the project stays the same and false - otherwise.
|
||||
|
@ -1528,6 +1605,8 @@ namespace ts.server {
|
|||
this.isInitialLoadPending = returnFalse;
|
||||
const reloadLevel = this.pendingReload;
|
||||
this.pendingReload = ConfigFileProgramReloadLevel.None;
|
||||
this.projectReferenceCallbacks = undefined;
|
||||
this.mapOfDeclarationDirectories = undefined;
|
||||
let result: boolean;
|
||||
switch (reloadLevel) {
|
||||
case ConfigFileProgramReloadLevel.Partial:
|
||||
|
@ -1570,6 +1649,12 @@ namespace ts.server {
|
|||
return program && program.forEachResolvedProjectReference(cb);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined {
|
||||
const program = this.getCurrentProgram();
|
||||
return program && program.getResolvedProjectReferenceToRedirect(fileName);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
|
||||
const host = this.projectService.host;
|
||||
|
@ -1652,6 +1737,8 @@ namespace ts.server {
|
|||
this.stopWatchingWildCards();
|
||||
this.projectErrors = undefined;
|
||||
this.configFileSpecs = undefined;
|
||||
this.projectReferenceCallbacks = undefined;
|
||||
this.mapOfDeclarationDirectories = undefined;
|
||||
super.close();
|
||||
}
|
||||
|
||||
|
|
|
@ -495,15 +495,17 @@ namespace ts.server {
|
|||
// the default project; if no configured projects, the first external project should
|
||||
// be the default project; otherwise the first inferred project should be the default.
|
||||
let firstExternalProject;
|
||||
let firstConfiguredProject;
|
||||
for (const project of this.containingProjects) {
|
||||
if (project.projectKind === ProjectKind.Configured) {
|
||||
return project;
|
||||
if (!project.isSourceOfProjectReferenceRedirect(this.fileName)) return project;
|
||||
if (!firstConfiguredProject) firstConfiguredProject = project;
|
||||
}
|
||||
else if (project.projectKind === ProjectKind.External && !firstExternalProject) {
|
||||
firstExternalProject = project;
|
||||
}
|
||||
}
|
||||
return firstExternalProject || this.containingProjects[0];
|
||||
return firstConfiguredProject || firstExternalProject || this.containingProjects[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -448,7 +448,9 @@ namespace ts.server {
|
|||
|
||||
function getDefinitionInProject(definition: DocumentPosition | undefined, definingProject: Project, project: Project): DocumentPosition | undefined {
|
||||
if (!definition || project.containsFile(toNormalizedPath(definition.fileName))) return definition;
|
||||
const mappedDefinition = definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition);
|
||||
const mappedDefinition = definingProject.isSourceOfProjectReferenceRedirect(definition.fileName) ?
|
||||
definition :
|
||||
definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition);
|
||||
return mappedDefinition && project.containsFile(toNormalizedPath(mappedDefinition.fileName)) ? mappedDefinition : undefined;
|
||||
}
|
||||
|
||||
|
@ -477,7 +479,7 @@ namespace ts.server {
|
|||
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
|
||||
});
|
||||
}
|
||||
return originalLocation;
|
||||
return originalLocation === location ? undefined : originalLocation;
|
||||
});
|
||||
return toDo;
|
||||
}
|
||||
|
@ -1037,7 +1039,9 @@ namespace ts.server {
|
|||
|
||||
private getEmitOutput(args: protocol.FileRequestArgs): EmitOutput {
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
return project.getLanguageService().getEmitOutput(file);
|
||||
return project.shouldEmitFile(project.getScriptInfo(file)) ?
|
||||
project.getLanguageService().getEmitOutput(file) :
|
||||
{ emitSkipped: true, outputFiles: [] };
|
||||
}
|
||||
|
||||
private mapDefinitionInfo(definitions: readonly DefinitionInfo[], project: Project): readonly protocol.FileSpanWithContext[] {
|
||||
|
@ -1672,10 +1676,10 @@ namespace ts.server {
|
|||
}
|
||||
}
|
||||
|
||||
private createCheckList(fileNames: string[], defaultProject?: Project): PendingErrorCheck[] {
|
||||
private createCheckList(fileNames: string[]): PendingErrorCheck[] {
|
||||
return mapDefined<string, PendingErrorCheck>(fileNames, uncheckedFileName => {
|
||||
const fileName = toNormalizedPath(uncheckedFileName);
|
||||
const project = defaultProject || this.projectService.tryGetDefaultProjectForFile(fileName);
|
||||
const project = this.projectService.tryGetDefaultProjectForFile(fileName);
|
||||
return project && { fileName, project };
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1149,10 +1149,10 @@ namespace ts {
|
|||
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
|
||||
getCurrentDirectory: () => currentDirectory,
|
||||
getProgram,
|
||||
fileExists: host.fileExists && (f => host.fileExists!(f)),
|
||||
readFile: host.readFile && ((f, encoding) => host.readFile!(f, encoding)),
|
||||
getDocumentPositionMapper: host.getDocumentPositionMapper && ((generatedFileName, sourceFileName) => host.getDocumentPositionMapper!(generatedFileName, sourceFileName)),
|
||||
getSourceFileLike: host.getSourceFileLike && (f => host.getSourceFileLike!(f)),
|
||||
fileExists: maybeBind(host, host.fileExists),
|
||||
readFile: maybeBind(host, host.readFile),
|
||||
getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper),
|
||||
getSourceFileLike: maybeBind(host, host.getSourceFileLike),
|
||||
log
|
||||
});
|
||||
|
||||
|
@ -1250,6 +1250,12 @@ namespace ts {
|
|||
if (host.resolveTypeReferenceDirectives) {
|
||||
compilerHost.resolveTypeReferenceDirectives = (...args) => host.resolveTypeReferenceDirectives!(...args);
|
||||
}
|
||||
if (host.setResolvedProjectReferenceCallbacks) {
|
||||
compilerHost.setResolvedProjectReferenceCallbacks = callbacks => host.setResolvedProjectReferenceCallbacks!(callbacks);
|
||||
}
|
||||
if (host.useSourceOfProjectReferenceRedirect) {
|
||||
compilerHost.useSourceOfProjectReferenceRedirect = () => host.useSourceOfProjectReferenceRedirect!();
|
||||
}
|
||||
|
||||
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
|
||||
const options: CreateProgramOptions = {
|
||||
|
|
|
@ -70,6 +70,11 @@ namespace ts {
|
|||
if (!sourceFile) return undefined;
|
||||
|
||||
const program = host.getProgram()!;
|
||||
// If this is source file of project reference source (instead of redirect) there is no generated position
|
||||
if (program.isSourceOfProjectReferenceRedirect(sourceFile.fileName)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const options = program.getCompilerOptions();
|
||||
const outPath = options.outFile || options.out;
|
||||
|
||||
|
|
|
@ -234,6 +234,10 @@ namespace ts {
|
|||
getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined;
|
||||
/* @internal */
|
||||
getSourceFileLike?(fileName: string): SourceFileLike | undefined;
|
||||
/* @internal */
|
||||
setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void;
|
||||
/* @internal */
|
||||
useSourceOfProjectReferenceRedirect?(): boolean;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
|
|
@ -145,6 +145,8 @@
|
|||
"unittests/tsserver/occurences.ts",
|
||||
"unittests/tsserver/openFile.ts",
|
||||
"unittests/tsserver/projectErrors.ts",
|
||||
"unittests/tsserver/projectReferenceCompileOnSave.ts",
|
||||
"unittests/tsserver/projectReferenceErrors.ts",
|
||||
"unittests/tsserver/projectReferences.ts",
|
||||
"unittests/tsserver/projects.ts",
|
||||
"unittests/tsserver/refactors.ts",
|
||||
|
|
|
@ -2,18 +2,12 @@ namespace ts.tscWatch {
|
|||
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
|
||||
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
|
||||
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
|
||||
type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map<true>; };
|
||||
type TsBuildWatchSystem = TestFSWithWatch.TestServerHostTrackingWrittenFiles;
|
||||
|
||||
function createTsBuildWatchSystem(fileOrFolderList: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) {
|
||||
const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem;
|
||||
const originalWriteFile = host.writeFile;
|
||||
host.writtenFiles = createMap<true>();
|
||||
host.writeFile = (fileName, content) => {
|
||||
originalWriteFile.call(host, fileName, content);
|
||||
const path = host.toFullPath(fileName);
|
||||
host.writtenFiles.set(path, true);
|
||||
};
|
||||
return host;
|
||||
return TestFSWithWatch.changeToHostTrackingWrittenFiles(
|
||||
createWatchedSystem(fileOrFolderList, params)
|
||||
);
|
||||
}
|
||||
|
||||
export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], defaultOptions?: BuildOptions) {
|
||||
|
@ -710,8 +704,8 @@ let x: string = 10;`);
|
|||
const coreIndexDts = projectFileName(SubProject.core, "index.d.ts");
|
||||
const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts");
|
||||
const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts");
|
||||
const expectedWatchedFiles = () => [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase()));
|
||||
const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests));
|
||||
const expectedProjectFiles = () => [libFile, ...tests, ...logic.slice(1), ...core.slice(1, core.length - 1)].map(f => f.path);
|
||||
const expectedProgramFiles = () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts];
|
||||
|
||||
function createSolutionAndWatchMode() {
|
||||
|
@ -723,12 +717,19 @@ let x: string = 10;`);
|
|||
}
|
||||
|
||||
function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) {
|
||||
verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles().filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles(), expectedWatchedDirectoriesRecursive);
|
||||
verifyWatchesOfProject(
|
||||
host,
|
||||
withTsserver ?
|
||||
[...core.slice(0, core.length - 1), ...logic, tests[0], libFile].map(f => f.path.toLowerCase()) :
|
||||
[core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase())),
|
||||
expectedWatchedDirectoriesRecursive
|
||||
);
|
||||
}
|
||||
|
||||
function verifyScenario(
|
||||
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
|
||||
expectedFilesAfterEdit: () => readonly string[]
|
||||
expectedProgramFilesAfterEdit: () => readonly string[],
|
||||
expectedProjectFilesAfterEdit: () => readonly string[]
|
||||
) {
|
||||
it("with tsc-watch", () => {
|
||||
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
|
||||
|
@ -737,7 +738,7 @@ let x: string = 10;`);
|
|||
|
||||
host.checkTimeoutQueueLengthAndRun(1);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
checkProgramActualFiles(watch(), expectedFilesAfterEdit());
|
||||
checkProgramActualFiles(watch(), expectedProgramFilesAfterEdit());
|
||||
|
||||
});
|
||||
|
||||
|
@ -747,7 +748,7 @@ let x: string = 10;`);
|
|||
edit(host, solutionBuilder);
|
||||
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
checkProjectActualFiles(service, tests[0].path, [tests[0].path, ...expectedFilesAfterEdit()]);
|
||||
checkProjectActualFiles(service, tests[0].path, expectedProjectFilesAfterEdit());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -777,7 +778,7 @@ function foo() {
|
|||
|
||||
// not ideal, but currently because of d.ts but no new file is written
|
||||
// There will be timeout queued even though file contents are same
|
||||
}, expectedProgramFiles);
|
||||
}, expectedProgramFiles, expectedProjectFiles);
|
||||
});
|
||||
|
||||
describe("non local edit in ts file, rebuilds in watch compilation", () => {
|
||||
|
@ -787,7 +788,7 @@ export function gfoo() {
|
|||
}`);
|
||||
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath);
|
||||
solutionBuilder.buildNextInvalidatedProject();
|
||||
}, expectedProgramFiles);
|
||||
}, expectedProgramFiles, expectedProjectFiles);
|
||||
});
|
||||
|
||||
describe("change in project reference config file builds correctly", () => {
|
||||
|
@ -798,7 +799,7 @@ export function gfoo() {
|
|||
}));
|
||||
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath, ConfigFileProgramReloadLevel.Full);
|
||||
solutionBuilder.buildNextInvalidatedProject();
|
||||
}, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]);
|
||||
}, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")], expectedProjectFiles);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -888,7 +889,9 @@ export function gfoo() {
|
|||
const aDts = dtsFile(multiFolder ? "a/index" : "a"), bDts = dtsFile(multiFolder ? "b/index" : "b");
|
||||
const expectedFiles = [jsFile(multiFolder ? "a/index" : "a"), aDts, jsFile(multiFolder ? "b/index" : "b"), bDts, jsFile(multiFolder ? "c/index" : "c")];
|
||||
const expectedProgramFiles = [cTs.path, libFile.path, aDts, refs.path, bDts];
|
||||
const expectedProjectFiles = [cTs.path, libFile.path, aTs.path, refs.path, bTs.path];
|
||||
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
|
||||
const expectedProjectWatchedFiles = expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
|
||||
const expectedWatchedDirectories = multiFolder ? [
|
||||
getProjectPath(project).toLowerCase() // watches for directories created for resolution of b
|
||||
] : emptyArray;
|
||||
|
@ -926,22 +929,29 @@ export function gfoo() {
|
|||
}
|
||||
|
||||
function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: readonly string[]) {
|
||||
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos);
|
||||
verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos });
|
||||
}
|
||||
|
||||
function verifyServerState(
|
||||
host: TsBuildWatchSystem,
|
||||
service: projectSystem.TestProjectService,
|
||||
expectedProgramFiles: readonly string[],
|
||||
expectedWatchedFiles: readonly string[],
|
||||
expectedWatchedDirectoriesRecursive: readonly string[],
|
||||
orphanInfos?: readonly string[]) {
|
||||
checkProjectActualFiles(service, cTsconfig.path, expectedProgramFiles.concat(cTsconfig.path));
|
||||
const watchedFiles = expectedWatchedFiles.filter(f => f !== cTs.path.toLowerCase());
|
||||
if (orphanInfos) {
|
||||
interface VerifyServerState {
|
||||
host: TsBuildWatchSystem;
|
||||
service: projectSystem.TestProjectService;
|
||||
expectedProjectFiles: readonly string[];
|
||||
expectedProjectWatchedFiles: readonly string[];
|
||||
expectedWatchedDirectoriesRecursive: readonly string[];
|
||||
orphanInfos?: readonly string[];
|
||||
}
|
||||
function verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos }: VerifyServerState) {
|
||||
checkProjectActualFiles(service, cTsconfig.path, expectedProjectFiles.concat(cTsconfig.path));
|
||||
const watchedFiles = expectedProjectWatchedFiles.filter(f => f !== cTs.path.toLowerCase());
|
||||
const actualOrphan = arrayFrom(mapDefinedIterator(
|
||||
service.filenameToScriptInfo.values(),
|
||||
v => v.containingProjects.length === 0 ? v.fileName : undefined
|
||||
));
|
||||
assert.equal(actualOrphan.length, orphanInfos ? orphanInfos.length : 0, `Orphans found: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
|
||||
if (orphanInfos && orphanInfos.length) {
|
||||
for (const orphan of orphanInfos) {
|
||||
const info = service.getScriptInfoForPath(orphan as Path);
|
||||
assert.isDefined(info);
|
||||
assert.isDefined(info, `${orphan} expected to be present. Actual: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
|
||||
assert.equal(info!.containingProjects.length, 0);
|
||||
watchedFiles.push(orphan);
|
||||
}
|
||||
|
@ -949,16 +959,20 @@ export function gfoo() {
|
|||
verifyWatchesOfProject(host, watchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
|
||||
}
|
||||
|
||||
function verifyScenario(
|
||||
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
|
||||
expectedEditErrors: readonly string[],
|
||||
expectedProgramFiles: readonly string[],
|
||||
expectedWatchedFiles: readonly string[],
|
||||
expectedWatchedDirectoriesRecursive: readonly string[],
|
||||
dependencies: readonly [string, readonly string[]][],
|
||||
revert?: (host: TsBuildWatchSystem) => void,
|
||||
orphanInfosAfterEdit?: readonly string[],
|
||||
orphanInfosAfterRevert?: readonly string[]) {
|
||||
interface VerifyScenario {
|
||||
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void;
|
||||
expectedEditErrors: readonly string[];
|
||||
expectedProgramFiles: readonly string[];
|
||||
expectedProjectFiles: readonly string[];
|
||||
expectedWatchedFiles: readonly string[];
|
||||
expectedProjectWatchedFiles: readonly string[];
|
||||
expectedWatchedDirectoriesRecursive: readonly string[];
|
||||
dependencies: readonly [string, readonly string[]][];
|
||||
revert?: (host: TsBuildWatchSystem) => void;
|
||||
orphanInfosAfterEdit?: readonly string[];
|
||||
orphanInfosAfterRevert?: readonly string[];
|
||||
}
|
||||
function verifyScenario({ edit, expectedEditErrors, expectedProgramFiles, expectedProjectFiles, expectedWatchedFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, dependencies, revert, orphanInfosAfterEdit, orphanInfosAfterRevert }: VerifyScenario) {
|
||||
it("with tsc-watch", () => {
|
||||
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
|
||||
|
||||
|
@ -985,7 +999,7 @@ export function gfoo() {
|
|||
edit(host, solutionBuilder);
|
||||
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfosAfterEdit);
|
||||
verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos: orphanInfosAfterEdit });
|
||||
|
||||
if (revert) {
|
||||
revert(host);
|
||||
|
@ -1010,20 +1024,21 @@ export function gfoo() {
|
|||
});
|
||||
|
||||
describe("non local edit updates the program and watch correctly", () => {
|
||||
verifyScenario(
|
||||
(host, solutionBuilder) => {
|
||||
verifyScenario({
|
||||
edit: (host, solutionBuilder) => {
|
||||
// edit
|
||||
host.writeFile(bTs.path, `${bTs.content}
|
||||
export function gfoo() {
|
||||
}`);
|
||||
solutionBuilder.invalidateProject(bTsconfig.path.toLowerCase() as ResolvedConfigFilePath);
|
||||
host.writeFile(bTs.path, `${bTs.content}\nexport function gfoo() {\n}`);
|
||||
solutionBuilder.invalidateProject((bTsconfig.path.toLowerCase() as ResolvedConfigFilePath));
|
||||
solutionBuilder.buildNextInvalidatedProject();
|
||||
},
|
||||
emptyArray,
|
||||
expectedEditErrors: emptyArray,
|
||||
expectedProgramFiles,
|
||||
expectedProjectFiles,
|
||||
expectedWatchedFiles,
|
||||
expectedProjectWatchedFiles,
|
||||
expectedWatchedDirectoriesRecursive,
|
||||
defaultDependencies);
|
||||
dependencies: defaultDependencies
|
||||
});
|
||||
});
|
||||
|
||||
describe("edit on config file", () => {
|
||||
|
@ -1032,30 +1047,32 @@ export function gfoo() {
|
|||
path: getFilePathInProject(project, "nrefs/a.d.ts"),
|
||||
content: refs.content
|
||||
};
|
||||
verifyScenario(
|
||||
host => {
|
||||
verifyScenario({
|
||||
edit: host => {
|
||||
const cTsConfigJson = JSON.parse(cTsconfig.content);
|
||||
host.ensureFileOrFolder(nrefs);
|
||||
cTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
|
||||
host.writeFile(cTsconfig.path, JSON.stringify(cTsConfigJson));
|
||||
},
|
||||
emptyArray,
|
||||
expectedProgramFiles.map(nrefReplacer),
|
||||
expectedWatchedFiles.map(nrefReplacer),
|
||||
expectedWatchedDirectoriesRecursive.map(nrefReplacer),
|
||||
[
|
||||
expectedEditErrors: emptyArray,
|
||||
expectedProgramFiles: expectedProgramFiles.map(nrefReplacer),
|
||||
expectedProjectFiles: expectedProjectFiles.map(nrefReplacer),
|
||||
expectedWatchedFiles: expectedWatchedFiles.map(nrefReplacer),
|
||||
expectedProjectWatchedFiles: expectedProjectWatchedFiles.map(nrefReplacer),
|
||||
expectedWatchedDirectoriesRecursive: expectedWatchedDirectoriesRecursive.map(nrefReplacer),
|
||||
dependencies: [
|
||||
[aDts, [aDts]],
|
||||
[bDts, [bDts, aDts]],
|
||||
[nrefs.path, [nrefs.path]],
|
||||
[cTs.path, [cTs.path, nrefs.path, bDts]]
|
||||
],
|
||||
// revert the update
|
||||
host => host.writeFile(cTsconfig.path, cTsconfig.content),
|
||||
revert: host => host.writeFile(cTsconfig.path, cTsconfig.content),
|
||||
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
|
||||
[refs.path.toLowerCase()],
|
||||
orphanInfosAfterEdit: [refs.path.toLowerCase()],
|
||||
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
|
||||
[nrefs.path.toLowerCase()]
|
||||
);
|
||||
orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
|
||||
});
|
||||
});
|
||||
|
||||
describe("edit in referenced config file", () => {
|
||||
|
@ -1064,82 +1081,84 @@ export function gfoo() {
|
|||
content: "export declare class A {}"
|
||||
};
|
||||
const expectedProgramFiles = [cTs.path, bDts, nrefs.path, refs.path, libFile.path];
|
||||
const expectedProjectFiles = [cTs.path, bTs.path, nrefs.path, refs.path, libFile.path];
|
||||
const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
|
||||
verifyScenario(
|
||||
host => {
|
||||
verifyScenario({
|
||||
edit: host => {
|
||||
const bTsConfigJson = JSON.parse(bTsconfig.content);
|
||||
host.ensureFileOrFolder(nrefs);
|
||||
bTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
|
||||
host.writeFile(bTsconfig.path, JSON.stringify(bTsConfigJson));
|
||||
},
|
||||
emptyArray,
|
||||
expectedEditErrors: emptyArray,
|
||||
expectedProgramFiles,
|
||||
expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
|
||||
(multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive).concat(getFilePathInProject(project, "nrefs").toLowerCase()),
|
||||
[
|
||||
expectedProjectFiles,
|
||||
expectedWatchedFiles: expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
|
||||
expectedProjectWatchedFiles: expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
|
||||
expectedWatchedDirectoriesRecursive: (multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive).concat(getFilePathInProject(project, "nrefs").toLowerCase()),
|
||||
dependencies: [
|
||||
[nrefs.path, [nrefs.path]],
|
||||
[bDts, [bDts, nrefs.path]],
|
||||
[refs.path, [refs.path]],
|
||||
[cTs.path, [cTs.path, refs.path, bDts]],
|
||||
],
|
||||
// revert the update
|
||||
host => host.writeFile(bTsconfig.path, bTsconfig.content),
|
||||
revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
|
||||
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
|
||||
[aDts.toLowerCase()],
|
||||
orphanInfosAfterEdit: [aTs.path.toLowerCase()],
|
||||
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
|
||||
[nrefs.path.toLowerCase()]
|
||||
);
|
||||
orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleting referenced config file", () => {
|
||||
const expectedProgramFiles = [cTs.path, bTs.path, refs.path, libFile.path];
|
||||
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase());
|
||||
const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
|
||||
// Resolutions should change now
|
||||
// Should map to b.ts instead with options from our own config
|
||||
verifyScenario(
|
||||
host => host.deleteFile(bTsconfig.path),
|
||||
[
|
||||
verifyScenario({
|
||||
edit: host => host.deleteFile(bTsconfig.path),
|
||||
expectedEditErrors: [
|
||||
`${multiFolder ? "c/tsconfig.json" : "tsconfig.c.json"}(9,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "b" : "tsconfig.b.json"}' not found.\n`
|
||||
],
|
||||
expectedProgramFiles,
|
||||
expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase()),
|
||||
multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive,
|
||||
[
|
||||
expectedProjectFiles: expectedProgramFiles,
|
||||
expectedWatchedFiles,
|
||||
expectedProjectWatchedFiles: expectedWatchedFiles,
|
||||
expectedWatchedDirectoriesRecursive: multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive,
|
||||
dependencies: [
|
||||
[bTs.path, [bTs.path, refs.path]],
|
||||
[refs.path, [refs.path]],
|
||||
[cTs.path, [cTs.path, refs.path, bTs.path]],
|
||||
],
|
||||
// revert the update
|
||||
host => host.writeFile(bTsconfig.path, bTsconfig.content),
|
||||
revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
|
||||
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
|
||||
[bDts.toLowerCase(), aDts.toLowerCase(), aTsconfig.path.toLowerCase()],
|
||||
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
|
||||
[bTs.path.toLowerCase()]
|
||||
);
|
||||
orphanInfosAfterEdit: [aTs.path.toLowerCase(), aTsconfig.path.toLowerCase()],
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleting transitively referenced config file", () => {
|
||||
verifyScenario(
|
||||
host => host.deleteFile(aTsconfig.path),
|
||||
[
|
||||
verifyScenario({
|
||||
edit: host => host.deleteFile(aTsconfig.path),
|
||||
expectedEditErrors: [
|
||||
`${multiFolder ? "b/tsconfig.json" : "tsconfig.b.json"}(10,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "a" : "tsconfig.a.json"}' not found.\n`
|
||||
],
|
||||
expectedProgramFiles.map(s => s.replace(aDts, aTs.path)),
|
||||
expectedWatchedFiles.map(s => s.replace(aDts.toLowerCase(), aTs.path.toLocaleLowerCase())),
|
||||
expectedProgramFiles: expectedProgramFiles.map(s => s.replace(aDts, aTs.path)),
|
||||
expectedProjectFiles,
|
||||
expectedWatchedFiles: expectedWatchedFiles.map(s => s.replace(aDts.toLowerCase(), aTs.path.toLocaleLowerCase())),
|
||||
expectedProjectWatchedFiles,
|
||||
expectedWatchedDirectoriesRecursive,
|
||||
[
|
||||
dependencies: [
|
||||
[aTs.path, [aTs.path]],
|
||||
[bDts, [bDts, aTs.path]],
|
||||
[refs.path, [refs.path]],
|
||||
[cTs.path, [cTs.path, refs.path, bDts]],
|
||||
],
|
||||
// revert the update
|
||||
host => host.writeFile(aTsconfig.path, aTsconfig.content),
|
||||
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
|
||||
[aDts.toLowerCase()],
|
||||
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
|
||||
[aTs.path.toLowerCase()]
|
||||
);
|
||||
revert: host => host.writeFile(aTsconfig.path, aTsconfig.content),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -179,7 +179,7 @@ namespace ts.projectSystem {
|
|||
}
|
||||
|
||||
function verifyUserTsConfigProject(session: TestSession) {
|
||||
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aDts.path, userTsconfig.path]);
|
||||
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]);
|
||||
}
|
||||
|
||||
it("goToDefinition", () => {
|
||||
|
@ -450,6 +450,13 @@ namespace ts.projectSystem {
|
|||
name: "function f(): void",
|
||||
},
|
||||
references: [
|
||||
makeReferenceEntry({
|
||||
file: aTs,
|
||||
text: "f",
|
||||
options: { index: 1 },
|
||||
contextText: "function f() {}",
|
||||
isDefinition: true
|
||||
}),
|
||||
{
|
||||
fileName: bTs.path,
|
||||
isDefinition: false,
|
||||
|
@ -457,13 +464,6 @@ namespace ts.projectSystem {
|
|||
isWriteAccess: false,
|
||||
textSpan: { start: 0, length: 1 },
|
||||
},
|
||||
makeReferenceEntry({
|
||||
file: aTs,
|
||||
text: "f",
|
||||
options: { index: 1 },
|
||||
contextText: "function f() {}",
|
||||
isDefinition: true
|
||||
})
|
||||
],
|
||||
}
|
||||
]);
|
||||
|
|
|
@ -73,7 +73,16 @@ namespace ts.projectSystem {
|
|||
verifyEvent(project, `Change in config file detected`);
|
||||
});
|
||||
|
||||
it("when opening original location project", () => {
|
||||
describe("when opening original location project", () => {
|
||||
it("with project references", () => {
|
||||
verify();
|
||||
});
|
||||
|
||||
it("when disableSourceOfProjectReferenceRedirect is true", () => {
|
||||
verify(/*disableSourceOfProjectReferenceRedirect*/ true);
|
||||
});
|
||||
|
||||
function verify(disableSourceOfProjectReferenceRedirect?: true) {
|
||||
const aDTs: File = {
|
||||
path: `${projectRoot}/a/a.d.ts`,
|
||||
content: `export declare class A {
|
||||
|
@ -92,6 +101,11 @@ namespace ts.projectSystem {
|
|||
const configB: File = {
|
||||
path: configBPath,
|
||||
content: JSON.stringify({
|
||||
...(disableSourceOfProjectReferenceRedirect && {
|
||||
compilerOptions: {
|
||||
disableSourceOfProjectReferenceRedirect
|
||||
}
|
||||
}),
|
||||
references: [{ path: "../a" }]
|
||||
})
|
||||
};
|
||||
|
@ -110,7 +124,13 @@ namespace ts.projectSystem {
|
|||
checkNumberOfProjects(service, { configuredProjects: 2 });
|
||||
const project = service.configuredProjects.get(configA.path)!;
|
||||
assert.isDefined(project);
|
||||
verifyEvent(project, `Creating project for original file: ${aTs.path} for location: ${aDTs.path}`);
|
||||
verifyEvent(
|
||||
project,
|
||||
disableSourceOfProjectReferenceRedirect ?
|
||||
`Creating project for original file: ${aTs.path} for location: ${aDTs.path}` :
|
||||
`Creating project for original file: ${aTs.path}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
describe("with external projects and config files ", () => {
|
||||
|
|
|
@ -491,8 +491,8 @@ namespace ts.projectSystem {
|
|||
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: readonly string[]) {
|
||||
checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
|
||||
export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: readonly string[], additionInfo?: string) {
|
||||
checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
|
||||
}
|
||||
|
||||
export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location {
|
||||
|
@ -501,7 +501,7 @@ namespace ts.projectSystem {
|
|||
return protocolToLocation(str)(start);
|
||||
}
|
||||
|
||||
function protocolToLocation(text: string): (pos: number) => protocol.Location {
|
||||
export function protocolToLocation(text: string): (pos: number) => protocol.Location {
|
||||
const lineStarts = computeLineStarts(text);
|
||||
return pos => {
|
||||
const x = computeLineAndCharacterOfPosition(lineStarts, pos);
|
||||
|
|
|
@ -0,0 +1,410 @@
|
|||
namespace ts.projectSystem {
|
||||
describe("unittests:: tsserver:: with project references and compile on save", () => {
|
||||
const projectLocation = "/user/username/projects/myproject";
|
||||
const dependecyLocation = `${projectLocation}/dependency`;
|
||||
const usageLocation = `${projectLocation}/usage`;
|
||||
const dependencyTs: File = {
|
||||
path: `${dependecyLocation}/fns.ts`,
|
||||
content: `export function fn1() { }
|
||||
export function fn2() { }
|
||||
`
|
||||
};
|
||||
const dependencyConfig: File = {
|
||||
path: `${dependecyLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: { composite: true, declarationDir: "../decls" },
|
||||
compileOnSave: true
|
||||
})
|
||||
};
|
||||
const usageTs: File = {
|
||||
path: `${usageLocation}/usage.ts`,
|
||||
content: `import {
|
||||
fn1,
|
||||
fn2,
|
||||
} from '../decls/fns'
|
||||
fn1();
|
||||
fn2();
|
||||
`
|
||||
};
|
||||
const usageConfig: File = {
|
||||
path: `${usageLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compileOnSave: true,
|
||||
references: [{ path: "../dependency" }]
|
||||
})
|
||||
};
|
||||
|
||||
interface VerifySingleScenarioWorker extends VerifySingleScenario {
|
||||
withProject: boolean;
|
||||
}
|
||||
function verifySingleScenarioWorker({
|
||||
withProject, scenario, openFiles, requestArgs, change, expectedResult
|
||||
}: VerifySingleScenarioWorker) {
|
||||
it(scenario, () => {
|
||||
const host = TestFSWithWatch.changeToHostTrackingWrittenFiles(
|
||||
createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile])
|
||||
);
|
||||
const session = createSession(host);
|
||||
openFilesForSession(openFiles(), session);
|
||||
const reqArgs = requestArgs();
|
||||
const {
|
||||
expectedAffected,
|
||||
expectedEmit: { expectedEmitSuccess, expectedFiles },
|
||||
expectedEmitOutput
|
||||
} = expectedResult(withProject);
|
||||
|
||||
if (change) {
|
||||
session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
|
||||
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
|
||||
arguments: { file: dependencyTs.path }
|
||||
});
|
||||
const { file, insertString } = change();
|
||||
if (session.getProjectService().openFiles.has(file.path)) {
|
||||
const toLocation = protocolToLocation(file.content);
|
||||
const location = toLocation(file.content.length);
|
||||
session.executeCommandSeq<protocol.ChangeRequest>({
|
||||
command: protocol.CommandTypes.Change,
|
||||
arguments: {
|
||||
file: file.path,
|
||||
...location,
|
||||
endLine: location.line,
|
||||
endOffset: location.offset,
|
||||
insertString
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
host.writeFile(file.path, `${file.content}${insertString}`);
|
||||
}
|
||||
host.writtenFiles.clear();
|
||||
}
|
||||
|
||||
const args = withProject ? reqArgs : { file: reqArgs.file };
|
||||
// Verify CompileOnSaveAffectedFileList
|
||||
const actualAffectedFiles = session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
|
||||
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
|
||||
arguments: args
|
||||
}).response as protocol.CompileOnSaveAffectedFileListSingleProject[];
|
||||
assert.deepEqual(actualAffectedFiles, expectedAffected, "Affected files");
|
||||
|
||||
// Verify CompileOnSaveEmit
|
||||
const actualEmit = session.executeCommandSeq<protocol.CompileOnSaveEmitFileRequest>({
|
||||
command: protocol.CommandTypes.CompileOnSaveEmitFile,
|
||||
arguments: args
|
||||
}).response;
|
||||
assert.deepEqual(actualEmit, expectedEmitSuccess, "Emit files");
|
||||
assert.equal(host.writtenFiles.size, expectedFiles.length);
|
||||
for (const file of expectedFiles) {
|
||||
assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`);
|
||||
assert.isTrue(host.writtenFiles.has(file.path), `${file.path} is newly written`);
|
||||
}
|
||||
|
||||
// Verify EmitOutput
|
||||
const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq<protocol.EmitOutputRequest>({
|
||||
command: protocol.CommandTypes.EmitOutput,
|
||||
arguments: args
|
||||
}).response as EmitOutput;
|
||||
assert.deepEqual(actualEmitOutput, expectedEmitOutput, "Emit output");
|
||||
});
|
||||
}
|
||||
|
||||
interface VerifySingleScenario {
|
||||
scenario: string;
|
||||
openFiles: () => readonly File[];
|
||||
requestArgs: () => protocol.FileRequestArgs;
|
||||
skipWithoutProject?: boolean;
|
||||
change?: () => SingleScenarioChange;
|
||||
expectedResult: GetSingleScenarioResult;
|
||||
}
|
||||
function verifySingleScenario(scenario: VerifySingleScenario) {
|
||||
if (!scenario.skipWithoutProject) {
|
||||
describe("without specifying project file", () => {
|
||||
verifySingleScenarioWorker({
|
||||
withProject: false,
|
||||
...scenario
|
||||
});
|
||||
});
|
||||
}
|
||||
describe("with specifying project file", () => {
|
||||
verifySingleScenarioWorker({
|
||||
withProject: true,
|
||||
...scenario
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface SingleScenarioExpectedEmit {
|
||||
expectedEmitSuccess: boolean;
|
||||
expectedFiles: readonly File[];
|
||||
}
|
||||
interface SingleScenarioResult {
|
||||
expectedAffected: protocol.CompileOnSaveAffectedFileListSingleProject[];
|
||||
expectedEmit: SingleScenarioExpectedEmit;
|
||||
expectedEmitOutput: EmitOutput;
|
||||
}
|
||||
type GetSingleScenarioResult = (withProject: boolean) => SingleScenarioResult;
|
||||
interface SingleScenarioChange {
|
||||
file: File;
|
||||
insertString: string;
|
||||
}
|
||||
interface ScenarioDetails {
|
||||
scenarioName: string;
|
||||
requestArgs: () => protocol.FileRequestArgs;
|
||||
skipWithoutProject?: boolean;
|
||||
initial: GetSingleScenarioResult;
|
||||
localChangeToDependency: GetSingleScenarioResult;
|
||||
localChangeToUsage: GetSingleScenarioResult;
|
||||
changeToDependency: GetSingleScenarioResult;
|
||||
changeToUsage: GetSingleScenarioResult;
|
||||
}
|
||||
interface VerifyScenario {
|
||||
openFiles: () => readonly File[];
|
||||
scenarios: readonly ScenarioDetails[];
|
||||
}
|
||||
|
||||
const localChange = "function fn3() { }";
|
||||
const change = `export ${localChange}`;
|
||||
const changeJs = `function fn3() { }
|
||||
exports.fn3 = fn3;`;
|
||||
const changeDts = "export declare function fn3(): void;";
|
||||
function verifyScenario({ openFiles, scenarios }: VerifyScenario) {
|
||||
for (const {
|
||||
scenarioName, requestArgs, skipWithoutProject, initial,
|
||||
localChangeToDependency, localChangeToUsage,
|
||||
changeToDependency, changeToUsage
|
||||
} of scenarios) {
|
||||
describe(scenarioName, () => {
|
||||
verifySingleScenario({
|
||||
scenario: "with initial file open",
|
||||
openFiles,
|
||||
requestArgs,
|
||||
skipWithoutProject,
|
||||
expectedResult: initial
|
||||
});
|
||||
|
||||
verifySingleScenario({
|
||||
scenario: "with local change to dependency",
|
||||
openFiles,
|
||||
requestArgs,
|
||||
skipWithoutProject,
|
||||
change: () => ({ file: dependencyTs, insertString: localChange }),
|
||||
expectedResult: localChangeToDependency
|
||||
});
|
||||
|
||||
verifySingleScenario({
|
||||
scenario: "with local change to usage",
|
||||
openFiles,
|
||||
requestArgs,
|
||||
skipWithoutProject,
|
||||
change: () => ({ file: usageTs, insertString: localChange }),
|
||||
expectedResult: localChangeToUsage
|
||||
});
|
||||
|
||||
verifySingleScenario({
|
||||
scenario: "with change to dependency",
|
||||
openFiles,
|
||||
requestArgs,
|
||||
skipWithoutProject,
|
||||
change: () => ({ file: dependencyTs, insertString: change }),
|
||||
expectedResult: changeToDependency
|
||||
});
|
||||
|
||||
verifySingleScenario({
|
||||
scenario: "with change to usage",
|
||||
openFiles,
|
||||
requestArgs,
|
||||
skipWithoutProject,
|
||||
change: () => ({ file: usageTs, insertString: change }),
|
||||
expectedResult: changeToUsage
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function expectedAffectedFiles(config: File, fileNames: File[]): protocol.CompileOnSaveAffectedFileListSingleProject {
|
||||
return {
|
||||
projectFileName: config.path,
|
||||
fileNames: fileNames.map(f => f.path),
|
||||
projectUsesOutFile: false
|
||||
};
|
||||
}
|
||||
|
||||
function expectedUsageEmit(appendJsText?: string): SingleScenarioExpectedEmit {
|
||||
const appendJs = appendJsText ? `${appendJsText}
|
||||
` : "";
|
||||
return {
|
||||
expectedEmitSuccess: true,
|
||||
expectedFiles: [{
|
||||
path: `${usageLocation}/usage.js`,
|
||||
content: `"use strict";
|
||||
exports.__esModule = true;
|
||||
var fns_1 = require("../decls/fns");
|
||||
fns_1.fn1();
|
||||
fns_1.fn2();
|
||||
${appendJs}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
function expectedEmitOutput({ expectedFiles }: SingleScenarioExpectedEmit): EmitOutput {
|
||||
return {
|
||||
outputFiles: expectedFiles.map(({ path, content }) => ({
|
||||
name: path,
|
||||
text: content,
|
||||
writeByteOrderMark: false
|
||||
})),
|
||||
emitSkipped: false
|
||||
};
|
||||
}
|
||||
|
||||
function expectedUsageEmitOutput(appendJsText?: string): EmitOutput {
|
||||
return expectedEmitOutput(expectedUsageEmit(appendJsText));
|
||||
}
|
||||
|
||||
function noEmit(): SingleScenarioExpectedEmit {
|
||||
return {
|
||||
expectedEmitSuccess: false,
|
||||
expectedFiles: emptyArray
|
||||
};
|
||||
}
|
||||
|
||||
function noEmitOutput(): EmitOutput {
|
||||
return {
|
||||
emitSkipped: true,
|
||||
outputFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
function expectedDependencyEmit(appendJsText?: string, appendDtsText?: string): SingleScenarioExpectedEmit {
|
||||
const appendJs = appendJsText ? `${appendJsText}
|
||||
` : "";
|
||||
const appendDts = appendDtsText ? `${appendDtsText}
|
||||
` : "";
|
||||
return {
|
||||
expectedEmitSuccess: true,
|
||||
expectedFiles: [
|
||||
{
|
||||
path: `${dependecyLocation}/fns.js`,
|
||||
content: `"use strict";
|
||||
exports.__esModule = true;
|
||||
function fn1() { }
|
||||
exports.fn1 = fn1;
|
||||
function fn2() { }
|
||||
exports.fn2 = fn2;
|
||||
${appendJs}`
|
||||
},
|
||||
{
|
||||
path: `${projectLocation}/decls/fns.d.ts`,
|
||||
content: `export declare function fn1(): void;
|
||||
export declare function fn2(): void;
|
||||
${appendDts}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function expectedDependencyEmitOutput(appendJsText?: string, appendDtsText?: string): EmitOutput {
|
||||
return expectedEmitOutput(expectedDependencyEmit(appendJsText, appendDtsText));
|
||||
}
|
||||
|
||||
function scenarioDetailsOfUsage(isDependencyOpen?: boolean): ScenarioDetails[] {
|
||||
return [
|
||||
{
|
||||
scenarioName: "Of usageTs",
|
||||
requestArgs: () => ({ file: usageTs.path, projectFileName: usageConfig.path }),
|
||||
initial: () => initialUsageTs(),
|
||||
// no change to usage so same as initial only usage file
|
||||
localChangeToDependency: () => initialUsageTs(),
|
||||
localChangeToUsage: () => initialUsageTs(localChange),
|
||||
changeToDependency: () => initialUsageTs(),
|
||||
changeToUsage: () => initialUsageTs(changeJs)
|
||||
},
|
||||
{
|
||||
scenarioName: "Of dependencyTs in usage project",
|
||||
requestArgs: () => ({ file: dependencyTs.path, projectFileName: usageConfig.path }),
|
||||
skipWithoutProject: !!isDependencyOpen,
|
||||
initial: () => initialDependencyTs(),
|
||||
localChangeToDependency: () => initialDependencyTs(/*noUsageFiles*/ true),
|
||||
localChangeToUsage: () => initialDependencyTs(/*noUsageFiles*/ true),
|
||||
changeToDependency: () => initialDependencyTs(),
|
||||
changeToUsage: () => initialDependencyTs(/*noUsageFiles*/ true)
|
||||
}
|
||||
];
|
||||
|
||||
function initialUsageTs(jsText?: string) {
|
||||
return {
|
||||
expectedAffected: [
|
||||
expectedAffectedFiles(usageConfig, [usageTs])
|
||||
],
|
||||
expectedEmit: expectedUsageEmit(jsText),
|
||||
expectedEmitOutput: expectedUsageEmitOutput(jsText)
|
||||
};
|
||||
}
|
||||
|
||||
function initialDependencyTs(noUsageFiles?: true) {
|
||||
return {
|
||||
expectedAffected: [
|
||||
expectedAffectedFiles(usageConfig, noUsageFiles ? [] : [usageTs])
|
||||
],
|
||||
expectedEmit: noEmit(),
|
||||
expectedEmitOutput: noEmitOutput()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function scenarioDetailsOfDependencyWhenOpen(): ScenarioDetails {
|
||||
return {
|
||||
scenarioName: "Of dependencyTs",
|
||||
requestArgs: () => ({ file: dependencyTs.path, projectFileName: dependencyConfig.path }),
|
||||
initial,
|
||||
localChangeToDependency: withProject => ({
|
||||
expectedAffected: withProject ?
|
||||
[
|
||||
expectedAffectedFiles(dependencyConfig, [dependencyTs])
|
||||
] :
|
||||
[
|
||||
expectedAffectedFiles(usageConfig, []),
|
||||
expectedAffectedFiles(dependencyConfig, [dependencyTs])
|
||||
],
|
||||
expectedEmit: expectedDependencyEmit(localChange),
|
||||
expectedEmitOutput: expectedDependencyEmitOutput(localChange)
|
||||
}),
|
||||
localChangeToUsage: withProject => initial(withProject, /*noUsageFiles*/ true),
|
||||
changeToDependency: withProject => initial(withProject, /*noUsageFiles*/ undefined, changeJs, changeDts),
|
||||
changeToUsage: withProject => initial(withProject, /*noUsageFiles*/ true)
|
||||
};
|
||||
|
||||
function initial(withProject: boolean, noUsageFiles?: true, appendJs?: string, appendDts?: string): SingleScenarioResult {
|
||||
return {
|
||||
expectedAffected: withProject ?
|
||||
[
|
||||
expectedAffectedFiles(dependencyConfig, [dependencyTs])
|
||||
] :
|
||||
[
|
||||
expectedAffectedFiles(usageConfig, noUsageFiles ? [] : [usageTs]),
|
||||
expectedAffectedFiles(dependencyConfig, [dependencyTs])
|
||||
],
|
||||
expectedEmit: expectedDependencyEmit(appendJs, appendDts),
|
||||
expectedEmitOutput: expectedDependencyEmitOutput(appendJs, appendDts)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
describe("when dependency project is not open", () => {
|
||||
verifyScenario({
|
||||
openFiles: () => [usageTs],
|
||||
scenarios: scenarioDetailsOfUsage()
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the depedency file is open", () => {
|
||||
verifyScenario({
|
||||
openFiles: () => [usageTs, dependencyTs],
|
||||
scenarios: [
|
||||
...scenarioDetailsOfUsage(/*isDependencyOpen*/ true),
|
||||
scenarioDetailsOfDependencyWhenOpen(),
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
430
src/testRunner/unittests/tsserver/projectReferenceErrors.ts
Normal file
430
src/testRunner/unittests/tsserver/projectReferenceErrors.ts
Normal file
|
@ -0,0 +1,430 @@
|
|||
namespace ts.projectSystem {
|
||||
describe("unittests:: tsserver:: with project references and error reporting", () => {
|
||||
const projectLocation = "/user/username/projects/myproject";
|
||||
const dependecyLocation = `${projectLocation}/dependency`;
|
||||
const usageLocation = `${projectLocation}/usage`;
|
||||
|
||||
interface CheckErrorsInFile {
|
||||
session: TestSession;
|
||||
host: TestServerHost;
|
||||
expected: GetErrDiagnostics;
|
||||
expectedSequenceId?: number;
|
||||
}
|
||||
function checkErrorsInFile({ session, host, expected: { file, syntax, semantic, suggestion }, expectedSequenceId }: CheckErrorsInFile) {
|
||||
host.checkTimeoutQueueLengthAndRun(1);
|
||||
checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: syntax });
|
||||
session.clearMessages();
|
||||
|
||||
host.runQueuedImmediateCallbacks(1);
|
||||
checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: semantic });
|
||||
session.clearMessages();
|
||||
|
||||
host.runQueuedImmediateCallbacks(1);
|
||||
checkErrorMessage(session, "suggestionDiag", { file: file.path, diagnostics: suggestion });
|
||||
if (expectedSequenceId !== undefined) {
|
||||
checkCompleteEvent(session, 2, expectedSequenceId);
|
||||
}
|
||||
session.clearMessages();
|
||||
}
|
||||
|
||||
interface CheckAllErrors {
|
||||
session: TestSession;
|
||||
host: TestServerHost;
|
||||
expected: readonly GetErrDiagnostics[];
|
||||
expectedSequenceId: number;
|
||||
}
|
||||
function checkAllErrors({ session, host, expected, expectedSequenceId }: CheckAllErrors) {
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
checkErrorsInFile({
|
||||
session,
|
||||
host,
|
||||
expected: expected[i],
|
||||
expectedSequenceId: i === expected.length - 1 ? expectedSequenceId : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function verifyErrorsUsingGeterr({ allFiles, openFiles, expectedGetErr }: VerifyScenario) {
|
||||
it("verifies the errors in open file", () => {
|
||||
const host = createServerHost([...allFiles(), libFile]);
|
||||
const session = createSession(host, { canUseEvents: true, });
|
||||
openFilesForSession(openFiles(), session);
|
||||
|
||||
session.clearMessages();
|
||||
const expectedSequenceId = session.getNextSeq();
|
||||
const expected = expectedGetErr();
|
||||
session.executeCommandSeq<protocol.GeterrRequest>({
|
||||
command: protocol.CommandTypes.Geterr,
|
||||
arguments: {
|
||||
delay: 0,
|
||||
files: expected.map(f => f.file.path)
|
||||
}
|
||||
});
|
||||
|
||||
checkAllErrors({ session, host, expected, expectedSequenceId });
|
||||
});
|
||||
}
|
||||
|
||||
function verifyErrorsUsingGeterrForProject({ allFiles, openFiles, expectedGetErrForProject }: VerifyScenario) {
|
||||
it("verifies the errors in projects", () => {
|
||||
const host = createServerHost([...allFiles(), libFile]);
|
||||
const session = createSession(host, { canUseEvents: true, });
|
||||
openFilesForSession(openFiles(), session);
|
||||
|
||||
session.clearMessages();
|
||||
for (const expected of expectedGetErrForProject()) {
|
||||
const expectedSequenceId = session.getNextSeq();
|
||||
session.executeCommandSeq<protocol.GeterrForProjectRequest>({
|
||||
command: protocol.CommandTypes.GeterrForProject,
|
||||
arguments: {
|
||||
delay: 0,
|
||||
file: expected.project
|
||||
}
|
||||
});
|
||||
|
||||
checkAllErrors({ session, host, expected: expected.errors, expectedSequenceId });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function verifyErrorsUsingSyncMethods({ allFiles, openFiles, expectedSyncDiagnostics }: VerifyScenario) {
|
||||
it("verifies the errors using sync commands", () => {
|
||||
const host = createServerHost([...allFiles(), libFile]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession(openFiles(), session);
|
||||
for (const { file, project, syntax, semantic, suggestion } of expectedSyncDiagnostics()) {
|
||||
const actualSyntax = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({
|
||||
command: protocol.CommandTypes.SyntacticDiagnosticsSync,
|
||||
arguments: {
|
||||
file: file.path,
|
||||
projectFileName: project
|
||||
}
|
||||
}).response as protocol.Diagnostic[];
|
||||
assert.deepEqual(actualSyntax, syntax, `Syntax diagnostics for file: ${file.path}, project: ${project}`);
|
||||
const actualSemantic = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({
|
||||
command: protocol.CommandTypes.SemanticDiagnosticsSync,
|
||||
arguments: {
|
||||
file: file.path,
|
||||
projectFileName: project
|
||||
}
|
||||
}).response as protocol.Diagnostic[];
|
||||
assert.deepEqual(actualSemantic, semantic, `Semantic diagnostics for file: ${file.path}, project: ${project}`);
|
||||
const actualSuggestion = session.executeCommandSeq<protocol.SuggestionDiagnosticsSyncRequest>({
|
||||
command: protocol.CommandTypes.SuggestionDiagnosticsSync,
|
||||
arguments: {
|
||||
file: file.path,
|
||||
projectFileName: project
|
||||
}
|
||||
}).response as protocol.Diagnostic[];
|
||||
assert.deepEqual(actualSuggestion, suggestion, `Suggestion diagnostics for file: ${file.path}, project: ${project}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function verifyConfigFileErrors({ allFiles, openFiles, expectedConfigFileDiagEvents }: VerifyScenario) {
|
||||
it("verify config file errors", () => {
|
||||
const host = createServerHost([...allFiles(), libFile]);
|
||||
const { session, events } = createSessionWithEventTracking<server.ConfigFileDiagEvent>(host, server.ConfigFileDiagEvent);
|
||||
|
||||
for (const file of openFiles()) {
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: { file: file.path }
|
||||
});
|
||||
}
|
||||
|
||||
assert.deepEqual(events, expectedConfigFileDiagEvents().map(data => ({
|
||||
eventName: server.ConfigFileDiagEvent,
|
||||
data
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
interface GetErrDiagnostics {
|
||||
file: File;
|
||||
syntax: protocol.Diagnostic[];
|
||||
semantic: protocol.Diagnostic[];
|
||||
suggestion: protocol.Diagnostic[];
|
||||
}
|
||||
interface GetErrForProjectDiagnostics {
|
||||
project: string;
|
||||
errors: readonly GetErrDiagnostics[];
|
||||
}
|
||||
interface SyncDiagnostics extends GetErrDiagnostics {
|
||||
project?: string;
|
||||
}
|
||||
interface VerifyScenario {
|
||||
allFiles: () => readonly File[];
|
||||
openFiles: () => readonly File[];
|
||||
expectedGetErr: () => readonly GetErrDiagnostics[];
|
||||
expectedGetErrForProject: () => readonly GetErrForProjectDiagnostics[];
|
||||
expectedSyncDiagnostics: () => readonly SyncDiagnostics[];
|
||||
expectedConfigFileDiagEvents: () => readonly server.ConfigFileDiagEvent["data"][];
|
||||
}
|
||||
function verifyScenario(scenario: VerifyScenario) {
|
||||
verifyErrorsUsingGeterr(scenario);
|
||||
verifyErrorsUsingGeterrForProject(scenario);
|
||||
verifyErrorsUsingSyncMethods(scenario);
|
||||
verifyConfigFileErrors(scenario);
|
||||
}
|
||||
|
||||
function emptyDiagnostics(file: File): GetErrDiagnostics {
|
||||
return {
|
||||
file,
|
||||
syntax: emptyArray,
|
||||
semantic: emptyArray,
|
||||
suggestion: emptyArray
|
||||
};
|
||||
}
|
||||
|
||||
function syncDiagnostics(diagnostics: GetErrDiagnostics, project: string): SyncDiagnostics {
|
||||
return { project, ...diagnostics };
|
||||
}
|
||||
|
||||
interface VerifyUsageAndDependency {
|
||||
allFiles: readonly [File, File, File, File]; // dependencyTs, dependencyConfig, usageTs, usageConfig
|
||||
usageDiagnostics(): GetErrDiagnostics;
|
||||
dependencyDiagnostics(): GetErrDiagnostics;
|
||||
|
||||
}
|
||||
function verifyUsageAndDependency({ allFiles, usageDiagnostics, dependencyDiagnostics }: VerifyUsageAndDependency) {
|
||||
const [dependencyTs, dependencyConfig, usageTs, usageConfig] = allFiles;
|
||||
function usageProjectDiagnostics(): GetErrForProjectDiagnostics {
|
||||
return {
|
||||
project: usageTs.path,
|
||||
errors: [
|
||||
usageDiagnostics(),
|
||||
emptyDiagnostics(dependencyTs)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function dependencyProjectDiagnostics(): GetErrForProjectDiagnostics {
|
||||
return {
|
||||
project: dependencyTs.path,
|
||||
errors: [
|
||||
dependencyDiagnostics()
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function usageConfigDiag(): server.ConfigFileDiagEvent["data"] {
|
||||
return {
|
||||
triggerFile: usageTs.path,
|
||||
configFileName: usageConfig.path,
|
||||
diagnostics: emptyArray
|
||||
};
|
||||
}
|
||||
|
||||
function dependencyConfigDiag(): server.ConfigFileDiagEvent["data"] {
|
||||
return {
|
||||
triggerFile: dependencyTs.path,
|
||||
configFileName: dependencyConfig.path,
|
||||
diagnostics: emptyArray
|
||||
};
|
||||
}
|
||||
|
||||
describe("when dependency project is not open", () => {
|
||||
verifyScenario({
|
||||
allFiles: () => allFiles,
|
||||
openFiles: () => [usageTs],
|
||||
expectedGetErr: () => [
|
||||
usageDiagnostics()
|
||||
],
|
||||
expectedGetErrForProject: () => [
|
||||
usageProjectDiagnostics(),
|
||||
{
|
||||
project: dependencyTs.path,
|
||||
errors: [
|
||||
emptyDiagnostics(dependencyTs),
|
||||
usageDiagnostics()
|
||||
]
|
||||
}
|
||||
],
|
||||
expectedSyncDiagnostics: () => [
|
||||
// Without project
|
||||
usageDiagnostics(),
|
||||
emptyDiagnostics(dependencyTs),
|
||||
// With project
|
||||
syncDiagnostics(usageDiagnostics(), usageConfig.path),
|
||||
syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path),
|
||||
],
|
||||
expectedConfigFileDiagEvents: () => [
|
||||
usageConfigDiag()
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the depedency file is open", () => {
|
||||
verifyScenario({
|
||||
allFiles: () => allFiles,
|
||||
openFiles: () => [usageTs, dependencyTs],
|
||||
expectedGetErr: () => [
|
||||
usageDiagnostics(),
|
||||
dependencyDiagnostics(),
|
||||
],
|
||||
expectedGetErrForProject: () => [
|
||||
usageProjectDiagnostics(),
|
||||
dependencyProjectDiagnostics()
|
||||
],
|
||||
expectedSyncDiagnostics: () => [
|
||||
// Without project
|
||||
usageDiagnostics(),
|
||||
dependencyDiagnostics(),
|
||||
// With project
|
||||
syncDiagnostics(usageDiagnostics(), usageConfig.path),
|
||||
syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path),
|
||||
syncDiagnostics(dependencyDiagnostics(), dependencyConfig.path),
|
||||
],
|
||||
expectedConfigFileDiagEvents: () => [
|
||||
usageConfigDiag(),
|
||||
dependencyConfigDiag()
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("with module scenario", () => {
|
||||
const dependencyTs: File = {
|
||||
path: `${dependecyLocation}/fns.ts`,
|
||||
content: `export function fn1() { }
|
||||
export function fn2() { }
|
||||
// Introduce error for fnErr import in main
|
||||
// export function fnErr() { }
|
||||
// Error in dependency ts file
|
||||
export let x: string = 10;`
|
||||
};
|
||||
const dependencyConfig: File = {
|
||||
path: `${dependecyLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({ compilerOptions: { composite: true, declarationDir: "../decls" } })
|
||||
};
|
||||
const usageTs: File = {
|
||||
path: `${usageLocation}/usage.ts`,
|
||||
content: `import {
|
||||
fn1,
|
||||
fn2,
|
||||
fnErr
|
||||
} from '../decls/fns'
|
||||
fn1();
|
||||
fn2();
|
||||
fnErr();
|
||||
`
|
||||
};
|
||||
const usageConfig: File = {
|
||||
path: `${usageLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: { composite: true },
|
||||
references: [{ path: "../dependency" }]
|
||||
})
|
||||
};
|
||||
function usageDiagnostics(): GetErrDiagnostics {
|
||||
return {
|
||||
file: usageTs,
|
||||
syntax: emptyArray,
|
||||
semantic: [
|
||||
createDiagnostic(
|
||||
{ line: 4, offset: 5 },
|
||||
{ line: 4, offset: 10 },
|
||||
Diagnostics.Module_0_has_no_exported_member_1,
|
||||
[`"../dependency/fns"`, "fnErr"],
|
||||
"error",
|
||||
)
|
||||
],
|
||||
suggestion: emptyArray
|
||||
};
|
||||
}
|
||||
|
||||
function dependencyDiagnostics(): GetErrDiagnostics {
|
||||
return {
|
||||
file: dependencyTs,
|
||||
syntax: emptyArray,
|
||||
semantic: [
|
||||
createDiagnostic(
|
||||
{ line: 6, offset: 12 },
|
||||
{ line: 6, offset: 13 },
|
||||
Diagnostics.Type_0_is_not_assignable_to_type_1,
|
||||
["10", "string"],
|
||||
"error",
|
||||
)
|
||||
],
|
||||
suggestion: emptyArray
|
||||
};
|
||||
}
|
||||
|
||||
verifyUsageAndDependency({
|
||||
allFiles: [dependencyTs, dependencyConfig, usageTs, usageConfig],
|
||||
usageDiagnostics,
|
||||
dependencyDiagnostics
|
||||
});
|
||||
});
|
||||
|
||||
describe("with non module --out", () => {
|
||||
const dependencyTs: File = {
|
||||
path: `${dependecyLocation}/fns.ts`,
|
||||
content: `function fn1() { }
|
||||
function fn2() { }
|
||||
// Introduce error for fnErr import in main
|
||||
// function fnErr() { }
|
||||
// Error in dependency ts file
|
||||
let x: string = 10;`
|
||||
};
|
||||
const dependencyConfig: File = {
|
||||
path: `${dependecyLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({ compilerOptions: { composite: true, outFile: "../dependency.js" } })
|
||||
};
|
||||
const usageTs: File = {
|
||||
path: `${usageLocation}/usage.ts`,
|
||||
content: `fn1();
|
||||
fn2();
|
||||
fnErr();
|
||||
`
|
||||
};
|
||||
const usageConfig: File = {
|
||||
path: `${usageLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: { composite: true, outFile: "../usage.js" },
|
||||
references: [{ path: "../dependency" }]
|
||||
})
|
||||
};
|
||||
function usageDiagnostics(): GetErrDiagnostics {
|
||||
return {
|
||||
file: usageTs,
|
||||
syntax: emptyArray,
|
||||
semantic: [
|
||||
createDiagnostic(
|
||||
{ line: 3, offset: 1 },
|
||||
{ line: 3, offset: 6 },
|
||||
Diagnostics.Cannot_find_name_0,
|
||||
["fnErr"],
|
||||
"error",
|
||||
)
|
||||
],
|
||||
suggestion: emptyArray
|
||||
};
|
||||
}
|
||||
|
||||
function dependencyDiagnostics(): GetErrDiagnostics {
|
||||
return {
|
||||
file: dependencyTs,
|
||||
syntax: emptyArray,
|
||||
semantic: [
|
||||
createDiagnostic(
|
||||
{ line: 6, offset: 5 },
|
||||
{ line: 6, offset: 6 },
|
||||
Diagnostics.Type_0_is_not_assignable_to_type_1,
|
||||
["10", "string"],
|
||||
"error",
|
||||
)
|
||||
],
|
||||
suggestion: emptyArray
|
||||
};
|
||||
}
|
||||
|
||||
verifyUsageAndDependency({
|
||||
allFiles: [dependencyTs, dependencyConfig, usageTs, usageConfig],
|
||||
usageDiagnostics,
|
||||
dependencyDiagnostics
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -2567,6 +2567,7 @@ declare namespace ts {
|
|||
emitDeclarationOnly?: boolean;
|
||||
declarationDir?: string;
|
||||
disableSizeLimit?: boolean;
|
||||
disableSourceOfProjectReferenceRedirect?: boolean;
|
||||
downlevelIteration?: boolean;
|
||||
emitBOM?: boolean;
|
||||
emitDecoratorMetadata?: boolean;
|
||||
|
@ -8529,7 +8530,6 @@ declare namespace ts.server {
|
|||
getGlobalProjectErrors(): readonly Diagnostic[];
|
||||
getAllProjectErrors(): readonly Diagnostic[];
|
||||
getLanguageService(ensureSynchronized?: boolean): LanguageService;
|
||||
private shouldEmitFile;
|
||||
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[];
|
||||
/**
|
||||
* Returns true if emit was conducted
|
||||
|
@ -8610,11 +8610,25 @@ declare namespace ts.server {
|
|||
private typeAcquisition;
|
||||
private directoriesWatchedForWildcards;
|
||||
readonly canonicalConfigFilePath: NormalizedPath;
|
||||
private projectReferenceCallbacks;
|
||||
private mapOfDeclarationDirectories;
|
||||
/** Ref count to the project when opened from external project */
|
||||
private externalProjectRefCount;
|
||||
private projectErrors;
|
||||
private projectReferences;
|
||||
protected isInitialLoadPending: () => boolean;
|
||||
/**
|
||||
* This implementation of fileExists checks if the file being requested is
|
||||
* .d.ts file for the referenced Project.
|
||||
* If it is it returns true irrespective of whether that file exists on host
|
||||
*/
|
||||
fileExists(file: string): boolean;
|
||||
/**
|
||||
* This implementation of directoryExists checks if the directory being requested is
|
||||
* directory of .d.ts file for the referenced Project.
|
||||
* If it is it returns true irrespective of whether that directory exists on host
|
||||
*/
|
||||
directoryExists(path: string): boolean;
|
||||
/**
|
||||
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
|
||||
* @returns: true if set of files in the project stays the same and false - otherwise.
|
||||
|
|
|
@ -2567,6 +2567,7 @@ declare namespace ts {
|
|||
emitDeclarationOnly?: boolean;
|
||||
declarationDir?: string;
|
||||
disableSizeLimit?: boolean;
|
||||
disableSourceOfProjectReferenceRedirect?: boolean;
|
||||
downlevelIteration?: boolean;
|
||||
emitBOM?: boolean;
|
||||
emitDecoratorMetadata?: boolean;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"disableSourceOfProjectReferenceRedirect": true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue