Skip to content

Project References Core Support#22420

Closed
RyanCavanaugh wants to merge 35 commits intomicrosoft:masterfrom
RyanCavanaugh:projectReferencesPR
Closed

Project References Core Support#22420
RyanCavanaugh wants to merge 35 commits intomicrosoft:masterfrom
RyanCavanaugh:projectReferencesPR

Conversation

@RyanCavanaugh
Copy link
Member

Fixes most of #3469

There are two separable but intertwined concepts present in this PR: Project references, and composite projects.

A project reference is an entry in a tsconfig file that points to another tsconfig file:

/* tsconfig.json */
{
   // ...
       "projectReferences": [
           /* Looks for ../other/dir/tsconfig.json */
           /* Could also be a filename rather than directory */
           { "path": "../other/dir" }
       ]
}

A project reference in a tsconfig file performs a redirection when resolving module names. Writing writing:

   import expr from "../other/dir/mod"

If a project reference subsumes ../other or ../other/dir, instead of looking for mod.ts/mod.tsx files in ../other/dir, we look for .d.ts files in the outDir of the referenced project.

In this PR, project references may not be circular.

Project references may include prepend: true in outFile compilations. The output of a prepend: true is prepended to the output of the outFile of the current project. Additionally, we write a .bundle_info file indicating where "this" project's JS starts so that tools (see tsbuild below) can re-use the downstream project JS if the upstream project JS changes without modifying its .d.ts content.

In order to be referenced, a tsconfig file must be a composite project. This is done by setting "composite": true (including possibly in a base tsconfig file). Composite projects are subject to additional constraints:

  • rootDir is inferred differently if not specified
  • The list of compiled files must be calculable without parsing any actual source files
  • Declaration emit must be enabled
  • outFile and module compilation may not be mixed

These rules define an additional property of composite projects: They can be trivially determined to be up-to-date or not, because the set of input files and output files can be determined without reading any source files from disk.

tsbuild leverages these properties to enable extremely fast rebuilds of large solutions. The projectReferencesDogfood branch demonstrates what this looks like for an outFile compilation.

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Mar 14, 2018

As discussed offline, we need to handle scenarios with two tsconfig.jsons that model projects with side-by-side .spec.ts or .test.ts files.

category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Type_declaration_files_to_be_included_in_compilation
},
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this should be one level higher. it is similar to includes, excludes and files more than a compiler option.

Copy link
Member

@weswigham weswigham Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, having a references list implies composite, right? Can we combine the two, eg composite: [ /**/ ] for stuff with dependencies but composite: true or composite: [] for leaf nodes?


Edit from @RyanCavanaugh as I can't reply to this (???) - recycling bits:

References does not imply composite. Leaf-node projects may choose to have references without being composite (for example, it makes no sense to be forced to emit tsc.d.ts, but tsc's tsconfig reasonably has references to checker, etc.).

Copy link
Contributor

@mhegazy mhegazy Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one other reason why this should not be here is do not think we want tsc --references a\tsconfig.json to work... or is that a scenario we should support?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for moving this up a level.

References does not imply composite. Leaf-node projects may choose to have references without being composite (for example, it makes no sense to be forced to emit tsc.d.ts, but tsc's tsconfig reasonably has references to checker, etc.).

Definitely no references on the commandline.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

type: "string"
}
},
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we have this in both places?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed option from compilerOptions

context += resetEscapeSequence;
}

output += formatColorAndReset(relativeFileName, ForegroundColorEscapeSequences.Cyan);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so why did we change these?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing merge error or some kind?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mismerge

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Debug.assert(!!missingFilePaths);

// List of collected files is complete; validate exhautiveness if this is a project with a file list
if (options.composite && rootNames.length < files.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have done most of these project validation type checks in verifyCompilerOptions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved

// Project compilations never infer their root from the input source paths
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir || getDirectoryPath(normalizeSlashes(options.configFilePath)), currentDirectory);
}
else if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so what happenes if we set it if rootDir is set? we will report the error later on any ways..

the reason i ask, is it will make more sense to write it as:

if (options.rootDir) {
    commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
}
else if (options.composite) {
    commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)
}
else  {
    commonSourceDirectory = computeCommonSourceDirectory(emittedFiles);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to something similar

if (commonSourceDirectory === undefined) {
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary));
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
if (options.composite) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we check that we have a configFilePath? since if you have niether, normalizeSlashes will get undefined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an assert

};
}

function getPrependNodes(): PrependNode[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadonlyArray<PrependNode>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

walkProjectReferenceGraph(host, options, (_file, proj, opts) => {
if (opts.prepend) {
const dtsFilename = changeExtension(proj.outFile, ".d.ts");
const js = host.readFile(proj.outFile) || `/* Input file ${proj.outFile} was missing */\r\n`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the file does not exist, should we error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an error upstream

createDiagnosticForOptionName(Diagnostics.Option_paths_cannot_be_used_without_specifying_baseUrl_option, "paths");
}

if (options.composite) {
Copy link
Contributor

@mhegazy mhegazy Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be an error not to have --rootDir or a config file with composite as well. right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

composite is config only, so it's not an error to not have rootDir - composite just changes how it behaves

export function walkProjectReferenceGraph(host: CompilerHost, rootOptions: CompilerOptions,
callback: (resolvedFile: string, referencedProject: CompilerOptions, settings: ProjectReference) => void,
errorCallback?: (failedLocation: string) => void) {
if (rootOptions.references === undefined) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already check this in getProjectReferenceFileNames, no need to redo it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

errorCallback?: (failedLocation: string) => void) {
if (rootOptions.references === undefined) return;

const references = getProjectReferenceFileNames(host, rootOptions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are going to call resolveProjectReferencePath on every entry a few lines after this, why do we need this pre pass?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

}
const referenceJsonSource = parseJsonText(refPath, text);
const cmdLine = parseJsonSourceFileConfigFileContent(referenceJsonSource, configHost, getDirectoryPath(refPath), /*existingOptions*/ undefined, refPath);
cmdLine.options.configFilePath = refPath;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so why not pass the filepath to parseJsonSourceFileConfigFileContent instead?

};
}

export function getProjectReferenceFileNames(host: CompilerHost, rootOptions: CompilerOptions): string[] | undefined {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we even need this function? why not just expose walkProjectReferenceGraph, seems more efficient for most scenarios, and can decide what to do on missing references.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

return refPath;
}

export function walkProjectReferenceGraph(host: CompilerHost, rootOptions: CompilerOptions,
Copy link
Contributor

@mhegazy mhegazy Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for consistency with similar routines in the code base i would call this forEachReference or forEachProjectReference

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

// Note: We don't enumerate the input files to try to find corresponding .d.ts
// files because we can't know from the outside whether they're modules or not
if (referencedProject.outFile) {
const outFile = combinePaths(referencedProject.outDir, changeExtension(referencedProject.outFile, ".d.ts"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the outFile is not relative to the outDir. it is relative to the tsconfig.json file.. maybe i do not understand the rational here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can also depend on declarationDir.

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks like it needs to handle declarationDir all over (probably needs tests for that, too) - the rest is mostly just suggestions for improvements/opinions.

category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Type_declaration_files_to_be_included_in_compilation
},
{
Copy link
Member

@weswigham weswigham Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, having a references list implies composite, right? Can we combine the two, eg composite: [ /**/ ] for stuff with dependencies but composite: true or composite: [] for leaf nodes?


Edit from @RyanCavanaugh as I can't reply to this (???) - recycling bits:

References does not imply composite. Leaf-node projects may choose to have references without being composite (for example, it makes no sense to be forced to emit tsc.d.ts, but tsc's tsconfig reasonably has references to checker, etc.).

@@ -0,0 +1,2058 @@
/// <reference path="checker.ts"/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file shouldn't be here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

"category": "Message",
"code": 6300
},
"Referenced project '{0}' must have 'declaration': true": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or itself be composite (which implies this)? Should be mentioned in the error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this since it's vestigial from before we put everything under composite

"category": "Error",
"code": 6202
},
"Projects may not disable declaration emit.": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehhhhhhh...... I'd rather we just silently ate the declaration: false in this case and output the declarations into a temp folder, so the user never sees them (so as far as they're concerned, it is off).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd have to figure out what that would look like. tsc doesn't have an arbitrary other-place to drop things to. e.g. What happens if you have 2 checkouts of the same repo and do that - how do we tell them apart? Mirror the entire path from the root into your temp folder? Unlikely to work in Windows, at least, given path length limitations

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A dotfolder in the root of the compilation output, just like the bundle info?

const declarationFilePath = (forceDtsPaths || options.declaration) ? removeFileExtension(jsFilePath) + Extension.Dts : undefined;
const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath };
const bundleInfoPath = options.references && jsFilePath && (removeFileExtension(jsFilePath) + ".bundle_info");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: .tsbundle or maybe .tsbundledata instead of .bundle_info - because 1. I don't think people will know where a .bundle_info came from (and may delete it), and 2. I dislike underscore in extensions.

Actually - it might make sense to output into a .tscache folder adjacent to the output (and then the file can be named whatever), so the folder can be easily blanket-gitignored.

Copy link
Member Author

@RyanCavanaugh RyanCavanaugh Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to .tsbundleinfo

context += resetEscapeSequence;
}

output += formatColorAndReset(relativeFileName, ForegroundColorEscapeSequences.Cyan);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing merge error or some kind?

return undefined;
}
if (normalized.indexOf(k) === 0) {
result = changeExtension(fileName.replace(k, v), ".d.ts");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems ignorant of the target project's declarationDir setting - that seems important.

const nodes: PrependNode[] = [];
walkProjectReferenceGraph(host, options, (_file, proj, opts) => {
if (opts.prepend) {
const dtsFilename = changeExtension(proj.outFile, ".d.ts");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to account for declarationDir, and/or outDir, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outFile and declarationDir cannot be combined; ref #12405

outFile with outDir - doesn't matter; the output .d.ts file is dropped next to the output .js file

Need an error for a prepended reference that doesn't use outFile, though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️


function createMapping(resolvedFile: string, referencedProject: CompilerOptions, reference: ProjectReference) {
const rootDir = normalizePath(referencedProject.rootDir || getDirectoryPath(resolvedFile));
result.set(rootDir, referencedProject.outDir);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also declarationDir.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Debug.assert(!!missingFilePaths);

// List of collected files is complete; validate exhautiveness if this is a project with a file list
if (options.composite && rootNames.length < files.length) {
Copy link
Member

@weswigham weswigham Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to relax/remove this check by using preProcessFile (from preProcess.ts) from the services layer on the importing side to get the files a given file depends on (recursively) without doing a full parse (just a scan), for the most part. Still definitely a good practice to explicitly list all your files, but that could reduce friction in adopting the composite flag.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I did the dogfooding conversion, I just used --listFiles to fix the files list manually - seemed to be good enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any projects that depend on node modules for declarations and/or have a few hundred files I would think may not enjoy managing a list themselves - especially as the exact files in their modules shift under then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.d.ts files are not subject to this restriction

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a few hundred files, then your tsconfig would almost certainly be globbing. I'd look askance at anyone not globbing in that situation - detecting an orphaned .ts file that didn't get imported by anyone would be a big pit of failure.


function createMapping(resolvedFile: string, referencedProject: CompilerOptions, reference: ProjectReference) {
const rootDir = normalizePath(referencedProject.rootDir || getDirectoryPath(resolvedFile));
result.set(rootDir, referencedProject.outDir);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outDir can be empty. do we want to add a mapping to undefined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

return undefined;
}
if (normalized.indexOf(k) === 0) {
result = changeExtension(fileName.replace(k, v), ".d.ts");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do have a function to get the declaration file path, can we use that instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDeclarationEmitOutputFilePath is what i was referring to.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That function accepts a SourceFile, which we definitely don't have

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it would be easy to modify since only sourceFile.fileName is used in there anyways.


const referencedProjectOutFiles: Path[] = [];
const projectReferenceRedirects = createProjectReferenceRedirects(options);
checkProjectReferenceGraph();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do this in verifyCompilerOptions instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

}
},
{
name: "projects",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Copy link
Member Author

@RyanCavanaugh RyanCavanaugh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong button

};
}

function getPrependNodes(): PrependNode[] {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

@RyanCavanaugh
Copy link
Member Author

@weswigham @mhegazy I believe I've addressed all the comments (as well as offline discussion) - please have a 👀

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some quick fixes to maybe look at/style comments, but otherwise looks OK.


if (options.composite) {
if (options.declaration === false) {
createDiagnosticForOptionName(Diagnostics.Projects_may_not_disable_declaration_emit, "declaration");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Projects -> Composite projects?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️


function parseConfigHostFromCompilerHost(host: CompilerHost): ParseConfigFileHost {
return {
fileExists: host.fileExists,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fileExists(f): f => host.fileExists(f)

in case the host uses this.

return {
fileExists: host.fileExists,
readDirectory: () => [],
readFile: host.readFile,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow in case the host uses this.

readDirectory: () => [],
readFile: host.readFile,
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
getCurrentDirectory: host.getCurrentDirectory,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow in case the host uses this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ for all

}

function transformBundle(node: Bundle) {
return createBundle(node.sourceFiles.map(transformSourceFile), node.prepends);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe map(node.sourceFiles, transformSourceFile) instead of node.sourceFiles.map(transformSourceFile) in case someone decides to do something crazy like an emit with only unparsed sources.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

ES2017 = 4,
ES2018 = 5,
ESNext = 6,
JSON = 100,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting choice of number.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seemed like we should keep the ES versions adjacent 🤷‍♂️

performance.mark("beforeParse");
const result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind);
let result: SourceFile;
if (languageVersion === ScriptTarget.JSON) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already a ScriptKind.JSON, and there's already a scriptKind parameter - maybe this should just use that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The relevant incoming call stack here is via host.getSourceFile, which only accepts ScriptTarget rather than ScriptKind

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sheetalkamat that seems like the wrong place. Parser exposes parseSourceFile and parseJsonText separately, if someone wants to get back a JSON file they can call parseJsonText instead instead of having parseSourceFile reroute it

@@ -699,12 +699,14 @@ namespace ts.server {
}

private toFileSpan(fileName: string, textSpan: TextSpan, project: Project): protocol.FileSpan {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there other bits relying on script infos for line mapping that also need to be swapped?

@rbuckton
Copy link
Contributor

rbuckton commented May 3, 2018

@RyanCavanaugh I still owe you feedback on this. I should be able to submit comments by tomorrow.


const literalFiles = arrayFrom(literalFileMap.values());
const wildcardFiles = arrayFrom(wildcardFileMap.values());
const projectReferences = spec.referencesSpecs && spec.referencesSpecs.map((r): ProjectReference => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this here? This would get updated only when there is update to tsconfig.json. So you shouldnt want this in here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the comment - can you expand on that?

for (const ref of raw.references) {
if (typeof ref.path !== "string") {
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wont show up error at all in JSON file (with new api) do you have test case to verify this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how to test this. This call seems to match all the others in the file - what is the new API / what would cause this to not show up?

}

// Check if any referenced project tsconfig files are different
if (createProgramOptions.projectReferences) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just projectReferences instead ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


// Check if any referenced project tsconfig files are different
if (createProgramOptions.projectReferences) {
for (let i = 0; i < createProgramOptions.projectReferences.length; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a check when !projectReferences and oldProgram.projectReferences ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// Check if any referenced project tsconfig files are different
if (createProgramOptions.projectReferences) {
for (let i = 0; i < createProgramOptions.projectReferences.length; i++) {
const oldRef = resolvedProjectReferences[i];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is resolvedProjectReferences[i] is an oldRef?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - fixed

const refPath = resolveProjectReferencePath(host, ref);
// An absolute path pointing to the containing directory of the config file
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory());
const sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use: getParsedCommandLineOfConfigFile instead ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback from Mohamed was that we want to pass around SourceFiles instead of filenames so that in-memory editing of referenced projects would work

@@ -120,7 +120,7 @@ namespace ts {
createWatchOfConfigFile(configParseResult, commandLineOptions);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am assuming you arent adding this portion in this PR. Can you add a TODO and create a issue tracking that. Thanks

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

/**
* Normalizes a path including its leading drive letter (if any)
*/
export function normalizePathAndRoot(path: string): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the places I can find that reference this immediately call toLowerCase() on the result. I would just replace those calls with calls to normalizePath (or resolvePath) instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


for (const prepend of bundle.prepends) {
print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined);
write("\n");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

writeLine()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

case EmitHint.MappedTypeParameter: return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration));
case EmitHint.Unspecified: return pipelineEmitUnspecified(node);
default:
return Debug.assertNever(hint, `Unhandled EmitHint: ${(ts as any).EmitHint ? (ts as any).EmitHint[hint] : hint }`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take a look at how formatSyntaxKind works in utilities.ts and possibly add a format function for this.

@chriseppstein
Copy link

🎉🎉

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants