Skip to content

Commit b7f1c21

Browse files
author
Andy Hanson
committed
Merge branch 'master' into duplicatePackageImportFixes
2 parents 15b8af7 + e2a8f99 commit b7f1c21

29 files changed

+644
-184
lines changed

src/compiler/binder.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2072,7 +2072,10 @@ namespace ts {
20722072
if (isSpecialPropertyDeclaration(node as PropertyAccessExpression)) {
20732073
bindSpecialPropertyDeclaration(node as PropertyAccessExpression);
20742074
}
2075-
if (isInJavaScriptFile(node) && isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression)) {
2075+
if (isInJavaScriptFile(node) &&
2076+
file.commonJsModuleIndicator &&
2077+
isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) &&
2078+
!lookupSymbolForNameWorker(container, "module" as __String)) {
20762079
declareSymbol(container.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
20772080
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
20782081
}

src/compiler/program.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2712,12 +2712,12 @@ namespace ts {
27122712

27132713
function createDiagnosticForReference(index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) {
27142714
const referencesSyntax = getProjectReferencesSyntax();
2715-
if (referencesSyntax) {
2716-
if (createOptionDiagnosticInArrayLiteralSyntax(referencesSyntax, index, message, arg0, arg1)) {
2717-
return;
2718-
}
2715+
if (referencesSyntax && referencesSyntax.elements.length > index) {
2716+
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, referencesSyntax.elements[index], message, arg0, arg1));
2717+
}
2718+
else {
2719+
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1));
27192720
}
2720-
programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1));
27212721
}
27222722

27232723
function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number) {
@@ -2770,15 +2770,6 @@ namespace ts {
27702770
return !!props.length;
27712771
}
27722772

2773-
function createOptionDiagnosticInArrayLiteralSyntax(arrayLiteral: ArrayLiteralExpression, index: number, message: DiagnosticMessage, arg0: string | number | undefined, arg1?: string | number, arg2?: string | number): boolean {
2774-
if (arrayLiteral.elements.length <= index) {
2775-
// Out-of-bounds
2776-
return false;
2777-
}
2778-
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, arrayLiteral.elements[index], message, arg0, arg1, arg2));
2779-
return false; // TODO: GH#18217 This function always returns `false`!`
2780-
}
2781-
27822773
function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) {
27832774
hasEmitBlockingDiagnostics.set(toPath(emitFileName), true);
27842775
programDiagnostics.add(diag);

src/compiler/utilities.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -563,11 +563,6 @@ namespace ts {
563563
return isString(value) ? '"' + escapeNonAsciiString(value) + '"' : "" + value;
564564
}
565565

566-
// Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__'
567-
export function escapeLeadingUnderscores(identifier: string): __String {
568-
return (identifier.length >= 2 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ ? "_" + identifier : identifier) as __String;
569-
}
570-
571566
// Make an identifier from an external module name by extracting the string after the last "/" and replacing
572567
// all non-alphanumeric characters with underscores
573568
export function makeIdentifierFromModuleName(moduleName: string): string {
@@ -4804,6 +4799,11 @@ namespace ts {
48044799
return undefined;
48054800
}
48064801

4802+
/** Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__' */
4803+
export function escapeLeadingUnderscores(identifier: string): __String {
4804+
return (identifier.length >= 2 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ ? "_" + identifier : identifier) as __String;
4805+
}
4806+
48074807
/**
48084808
* Remove extra underscore from escaped identifier text content.
48094809
*

src/server/editorServices.ts

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,13 @@ namespace ts.server {
326326
syntaxOnly?: boolean;
327327
}
328328

329+
interface OriginalFileInfo { fileName: NormalizedPath; path: Path; }
330+
type OpenScriptInfoOrClosedFileInfo = ScriptInfo | OriginalFileInfo;
331+
332+
function isOpenScriptInfo(infoOrFileName: OpenScriptInfoOrClosedFileInfo): infoOrFileName is ScriptInfo {
333+
return !!(infoOrFileName as ScriptInfo).containingProjects;
334+
}
335+
329336
function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) {
330337
return `Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`;
331338
}
@@ -700,7 +707,7 @@ namespace ts.server {
700707

701708
/* @internal */
702709
private forEachProject(cb: (project: Project) => void) {
703-
for (const p of this.inferredProjects) cb(p);
710+
this.inferredProjects.forEach(cb);
704711
this.configuredProjects.forEach(cb);
705712
this.externalProjects.forEach(cb);
706713
}
@@ -1044,12 +1051,12 @@ namespace ts.server {
10441051
}
10451052
}
10461053

1047-
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
1054+
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: OpenScriptInfoOrClosedFileInfo) {
10481055
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
10491056
if (configFileExistenceInfo) {
10501057
// By default the info would get impacted by presence of config file since its in the detection path
10511058
// Only adding the info as a root to inferred project will need the existence to be watched by file watcher
1052-
if (!configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
1059+
if (isOpenScriptInfo(info) && !configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
10531060
configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, false);
10541061
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd);
10551062
}
@@ -1066,9 +1073,11 @@ namespace ts.server {
10661073
// Or the whole chain of config files for the roots of the inferred projects
10671074

10681075
// Cache the host value of file exists and add the info to map of open files impacted by this config file
1069-
const openFilesImpactedByConfigFile = createMap<boolean>();
1070-
openFilesImpactedByConfigFile.set(info.path, false);
10711076
const exists = this.host.fileExists(configFileName);
1077+
const openFilesImpactedByConfigFile = createMap<boolean>();
1078+
if (isOpenScriptInfo(info)) {
1079+
openFilesImpactedByConfigFile.set(info.path, false);
1080+
}
10721081
configFileExistenceInfo = { exists, openFilesImpactedByConfigFile };
10731082
this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo);
10741083
this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd);
@@ -1180,7 +1189,7 @@ namespace ts.server {
11801189
*/
11811190
private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) {
11821191
Debug.assert(!info.isScriptOpen());
1183-
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
1192+
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
11841193
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
11851194
if (configFileExistenceInfo) {
11861195
const infoIsRootOfInferredProject = configFileExistenceInfo.openFilesImpactedByConfigFile.get(info.path);
@@ -1214,7 +1223,7 @@ namespace ts.server {
12141223
/* @internal */
12151224
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
12161225
Debug.assert(info.isScriptOpen());
1217-
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
1226+
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
12181227
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
12191228
if (!configFileExistenceInfo) {
12201229
// Create the cache
@@ -1242,7 +1251,7 @@ namespace ts.server {
12421251
*/
12431252
/* @internal */
12441253
stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
1245-
this.forEachConfigFileLocation(info, /*infoShouldBeOpen*/ true, (configFileName, canonicalConfigFilePath) => {
1254+
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
12461255
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
12471256
if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) {
12481257
Debug.assert(info.isScriptOpen());
@@ -1265,12 +1274,12 @@ namespace ts.server {
12651274
* The server must start searching from the directory containing
12661275
* the newly opened file.
12671276
*/
1268-
private forEachConfigFileLocation(info: ScriptInfo, infoShouldBeOpen: boolean, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) {
1277+
private forEachConfigFileLocation(info: OpenScriptInfoOrClosedFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) {
12691278
if (this.syntaxOnly) {
12701279
return undefined;
12711280
}
12721281

1273-
Debug.assert(!infoShouldBeOpen || this.openFiles.has(info.path));
1282+
Debug.assert(!isOpenScriptInfo(info) || this.openFiles.has(info.path));
12741283
const projectRootPath = this.openFiles.get(info.path);
12751284

12761285
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
@@ -1309,11 +1318,13 @@ namespace ts.server {
13091318
* current directory (the directory in which tsc was invoked).
13101319
* The server must start searching from the directory containing
13111320
* the newly opened file.
1321+
* If script info is passed in, it is asserted to be open script info
1322+
* otherwise just file name
13121323
*/
1313-
private getConfigFileNameForFile(info: ScriptInfo, infoShouldBeOpen: boolean) {
1314-
if (infoShouldBeOpen) Debug.assert(info.isScriptOpen());
1324+
private getConfigFileNameForFile(info: OpenScriptInfoOrClosedFileInfo) {
1325+
if (isOpenScriptInfo(info)) Debug.assert(info.isScriptOpen());
13151326
this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`);
1316-
const configFileName = this.forEachConfigFileLocation(info, infoShouldBeOpen, (configFileName, canonicalConfigFilePath) =>
1327+
const configFileName = this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) =>
13171328
this.configFileExists(configFileName, canonicalConfigFilePath, info));
13181329
if (configFileName) {
13191330
this.logger.info(`For info: ${info.fileName} :: Config file name: ${configFileName}`);
@@ -1683,10 +1694,12 @@ namespace ts.server {
16831694
if (!this.eventHandler || this.suppressDiagnosticEvents) {
16841695
return;
16851696
}
1697+
const diagnostics = project.getLanguageService().getCompilerOptionsDiagnostics();
1698+
diagnostics.push(...project.getAllProjectErrors());
16861699

16871700
this.eventHandler(<ConfigFileDiagEvent>{
16881701
eventName: ConfigFileDiagEvent,
1689-
data: { configFileName: project.getConfigFilePath(), diagnostics: project.getAllProjectErrors(), triggerFile }
1702+
data: { configFileName: project.getConfigFilePath(), diagnostics, triggerFile }
16901703
});
16911704
}
16921705

@@ -2003,7 +2016,7 @@ namespace ts.server {
20032016
// we first detect if there is already a configured project created for it: if so,
20042017
// we re- read the tsconfig file content and update the project only if we havent already done so
20052018
// otherwise we create a new one.
2006-
const configFileName = this.getConfigFileNameForFile(info, /*infoShouldBeOpen*/ true);
2019+
const configFileName = this.getConfigFileNameForFile(info);
20072020
if (configFileName) {
20082021
const project = this.findConfiguredProjectByProjectName(configFileName);
20092022
if (!project) {
@@ -2091,17 +2104,40 @@ namespace ts.server {
20912104
return this.openClientFileWithNormalizedPath(toNormalizedPath(fileName), fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath ? toNormalizedPath(projectRootPath) : undefined);
20922105
}
20932106

2094-
/** @internal */
2095-
getProjectForFileWithoutOpening(fileName: NormalizedPath): { readonly scriptInfo: ScriptInfo, readonly projects: ReadonlyArray<Project> } | undefined {
2096-
const scriptInfo = this.filenameToScriptInfo.get(fileName) ||
2097-
this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName, this.currentDirectory, /*fileContent*/ undefined, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined);
2098-
if (!scriptInfo) return undefined;
2099-
if (scriptInfo.containingProjects.length) {
2100-
return { scriptInfo, projects: scriptInfo.containingProjects };
2107+
/*@internal*/
2108+
getOriginalLocationEnsuringConfiguredProject(project: Project, location: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined {
2109+
const originalLocation = project.getSourceMapper().tryGetOriginalLocation(location);
2110+
if (!originalLocation) return undefined;
2111+
2112+
const { fileName } = originalLocation;
2113+
if (!this.getScriptInfo(fileName) && !this.host.fileExists(fileName)) return undefined;
2114+
2115+
const originalFileInfo: OriginalFileInfo = { fileName: toNormalizedPath(fileName), path: this.toPath(fileName) };
2116+
const configFileName = this.getConfigFileNameForFile(originalFileInfo);
2117+
if (!configFileName) return undefined;
2118+
2119+
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
2120+
updateProjectIfDirty(configuredProject);
2121+
// Keep this configured project as referenced from project
2122+
addOriginalConfiguredProject(configuredProject);
2123+
2124+
const originalScriptInfo = this.getScriptInfo(fileName);
2125+
if (!originalScriptInfo || !originalScriptInfo.containingProjects.length) return undefined;
2126+
2127+
// Add configured projects as referenced
2128+
originalScriptInfo.containingProjects.forEach(project => {
2129+
if (project.projectKind === ProjectKind.Configured) {
2130+
addOriginalConfiguredProject(project as ConfiguredProject);
2131+
}
2132+
});
2133+
return originalLocation;
2134+
2135+
function addOriginalConfiguredProject(originalProject: ConfiguredProject) {
2136+
if (!project.originalConfiguredProjects) {
2137+
project.originalConfiguredProjects = createMap<true>();
2138+
}
2139+
project.originalConfiguredProjects.set(originalProject.canonicalConfigFilePath, true);
21012140
}
2102-
const configFileName = this.getConfigFileNameForFile(scriptInfo, /*infoShouldBeOpen*/ false);
2103-
const project = configFileName === undefined ? undefined : this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
2104-
return project && project.containsScriptInfo(scriptInfo) ? { scriptInfo, projects: [project] } : undefined;
21052141
}
21062142

21072143
/** @internal */
@@ -2126,7 +2162,7 @@ namespace ts.server {
21262162
this.openFiles.set(info.path, projectRootPath);
21272163
let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info);
21282164
if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization
2129-
configFileName = this.getConfigFileNameForFile(info, /*infoShouldBeOpen*/ true);
2165+
configFileName = this.getConfigFileNameForFile(info);
21302166
if (configFileName) {
21312167
project = this.findConfiguredProjectByProjectName(configFileName);
21322168
if (!project) {
@@ -2164,14 +2200,9 @@ namespace ts.server {
21642200
}
21652201
Debug.assert(!info.isOrphan());
21662202

2167-
// Remove the configured projects that have zero references from open files.
21682203
// This was postponed from closeOpenFile to after opening next file,
21692204
// so that we can reuse the project if we need to right away
2170-
this.configuredProjects.forEach(project => {
2171-
if (!project.hasOpenRef()) {
2172-
this.removeProject(project);
2173-
}
2174-
});
2205+
this.removeOrphanConfiguredProjects();
21752206

21762207
// Remove orphan inferred projects now that we have reused projects
21772208
// We need to create a duplicate because we cant guarantee order after removal
@@ -2199,6 +2230,30 @@ namespace ts.server {
21992230
return { configFileName, configFileErrors };
22002231
}
22012232

2233+
private removeOrphanConfiguredProjects() {
2234+
const toRemoveConfiguredProjects = cloneMap(this.configuredProjects);
2235+
2236+
// Do not remove configured projects that are used as original projects of other
2237+
this.inferredProjects.forEach(markOriginalProjectsAsUsed);
2238+
this.externalProjects.forEach(markOriginalProjectsAsUsed);
2239+
this.configuredProjects.forEach(project => {
2240+
// If project has open ref (there are more than zero references from external project/open file), keep it alive as well as any project it references
2241+
if (project.hasOpenRef()) {
2242+
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
2243+
markOriginalProjectsAsUsed(project);
2244+
}
2245+
});
2246+
2247+
// Remove all the non marked projects
2248+
toRemoveConfiguredProjects.forEach(project => this.removeProject(project));
2249+
2250+
function markOriginalProjectsAsUsed(project: Project) {
2251+
if (!project.isOrphan() && project.originalConfiguredProjects) {
2252+
project.originalConfiguredProjects.forEach((_value, configuredProjectPath) => toRemoveConfiguredProjects.delete(configuredProjectPath));
2253+
}
2254+
}
2255+
}
2256+
22022257
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {
22032258
if (this.syntaxOnly || !this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
22042259
return;

0 commit comments

Comments
 (0)