Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api for creating program in watch mode and using builder to get incremental emit/semantic diagnostics #20234

Merged
merged 48 commits into from
Jan 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a06f0c3
Use builder state to emit instead
sheetalkamat Oct 19, 2017
576fe1e
Expose the watch and builder API in the typescript.d.ts
sheetalkamat Oct 26, 2017
7ebf9d9
Lint errors fix
sheetalkamat Nov 7, 2017
3c5a6e1
Allow watch host to specify module name resolver
sheetalkamat Nov 7, 2017
c9a17f3
Add api to get the dependencies of the file
sheetalkamat Nov 9, 2017
6d36a3d
Make the versions in the source file non zero when the source file is…
sheetalkamat Nov 14, 2017
85ce1d0
Make the builder state as internal and expose builder instead of buil…
sheetalkamat Nov 14, 2017
e102fee
Use the results from affected file enumerator apis as Affected File r…
sheetalkamat Nov 15, 2017
ffa64e8
Set program as affected if emitting/diagnostics for whole program
sheetalkamat Nov 16, 2017
012f12b
To handle cancellation token, remove changed/affected files from the …
sheetalkamat Nov 23, 2017
0b79f4a
Handle emit only declaration file to always produce declaration file …
sheetalkamat Nov 23, 2017
374536b
Merge branch 'master' into builderApi
sheetalkamat Dec 4, 2017
3dda217
Rename getProgram to getExistingProgram
sheetalkamat Dec 4, 2017
61fc9b9
Rename Watch.synchronizeProgram to getProgram and return the updated …
sheetalkamat Dec 4, 2017
471c83b
Rename WatchHost.moduleNameResolver to WatchHost.resolveModuleNames t…
sheetalkamat Dec 4, 2017
1a91256
Make before and after program create callbacks optional
sheetalkamat Dec 4, 2017
f046d82
Merge branch 'master' into builderApi
sheetalkamat Dec 5, 2017
944f8b8
Instead of using system as object on WatchHost, create WatchCompilerH…
sheetalkamat Dec 5, 2017
43c2610
More functions moved from system to WatchCompilerHost
sheetalkamat Dec 6, 2017
e694b9e
Update the WatchCompilerHost creation
sheetalkamat Dec 6, 2017
8cc2936
Move watchFile and watchDirectory to WatchCompilerHost
sheetalkamat Dec 6, 2017
abafddd
Move internal functions in the watch to separate namespace
sheetalkamat Dec 6, 2017
77e6731
Handle setTimeout, clearTimeout, clearScreen and report watch Diagnos…
sheetalkamat Dec 6, 2017
d22ba5e
Move the system.write to trace on WatchCompilerHost
sheetalkamat Dec 6, 2017
c9a407e
Add getDefaultLibLocation and getDefaultLibFileName and remove system…
sheetalkamat Dec 6, 2017
14f66ef
Update the emitting file, reporting errors part of the watch api
sheetalkamat Dec 6, 2017
a21b074
Update the builder to take options aligning with the WatchCompilerHost
sheetalkamat Dec 6, 2017
eb052fe
Merge branch 'master' into builderApi
sheetalkamat Dec 6, 2017
39bf33d
Few renames
sheetalkamat Dec 7, 2017
4c21cbf
Create builderState so that when FilesAffectedBy is only api needed, …
sheetalkamat Dec 7, 2017
2586bb3
From builder use the builderState containing references and file infos
sheetalkamat Dec 7, 2017
bb0fc0d
Convert builder state to mutable data, so that later we can create bu…
sheetalkamat Dec 7, 2017
965f40f
Use builder state in the semantic/emit builder as well
sheetalkamat Dec 8, 2017
dc62bb9
Change builder to BuilderProgram so it is similar to operating on pro…
sheetalkamat Dec 8, 2017
9b54d2e
Create api to create Watch<BuilderProgram>
sheetalkamat Dec 8, 2017
8ad9a62
Api to get underlying program from builder
sheetalkamat Dec 8, 2017
a75badf
Rename on WatchBuilderProgram
sheetalkamat Dec 8, 2017
2611c9b
Merge branch 'master' into builderApi
sheetalkamat Dec 8, 2017
cb26366
When user provided resolution is used, invalidate resolutions for all…
sheetalkamat Dec 8, 2017
5bc78af
Merge branch 'master' into builderApi
sheetalkamat Jan 8, 2018
5bd3f97
Merge branch 'master' into builderApi
sheetalkamat Jan 16, 2018
6650029
Merge branch 'master' into builderApi
sheetalkamat Jan 16, 2018
ed23ca5
Merge branch 'master' into builderApi
sheetalkamat Jan 18, 2018
29dee9f
Do not expose createWatchOfConfigFile and createWatchOfFilesAndCompil…
sheetalkamat Jan 18, 2018
f29c0e3
Expose createWatchCompilerHost as overload
sheetalkamat Jan 18, 2018
bd43e45
Move getCurrentDirectory to builder program
sheetalkamat Jan 18, 2018
2be231d
Add createProgram on WatchCompilerHost
sheetalkamat Jan 19, 2018
8a51cda
Merge branch 'master' into builderApi
sheetalkamat Jan 19, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1199,23 +1199,12 @@ task("update-sublime", ["local", serverFile], function () {
});

var tslintRuleDir = "scripts/tslint/rules";
var tslintRules = [
"booleanTriviaRule",
"debugAssertRule",
"nextLineRule",
"noBomRule",
"noDoubleSpaceRule",
"noIncrementDecrementRule",
"noInOperatorRule",
"noTypeAssertionWhitespaceRule",
"objectLiteralSurroundingSpaceRule",
"typeOperatorSpacingRule",
];
var tslintRules = fs.readdirSync(tslintRuleDir);
var tslintRulesFiles = tslintRules.map(function (p) {
return path.join(tslintRuleDir, p + ".ts");
return path.join(tslintRuleDir, p);
});
var tslintRulesOutFiles = tslintRules.map(function (p) {
return path.join(builtLocalDirectory, "tslint/rules", p + ".js");
return path.join(builtLocalDirectory, "tslint/rules", p.replace(".ts", ".js"));
});
var tslintFormattersDir = "scripts/tslint/formatters";
var tslintFormatters = [
Expand Down
914 changes: 492 additions & 422 deletions src/compiler/builder.ts

Large diffs are not rendered by default.

384 changes: 384 additions & 0 deletions src/compiler/builderState.ts

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,10 +749,12 @@ namespace ts {
return _jsxNamespace;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

but if we try to emit this file with this emit resolver the emit will fail, e.g. marking aliases as referenced would not have happened.. 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.

Would not impact declaration file emit right? I mean there could be errors but the output wont change and hence ok to do so?

Copy link
Contributor

Choose a reason for hiding this comment

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

we need to document that well.. also maybe change the parameter name to something more declarative like declarationFileEmitOnly. i worry that someone will just create this and pass true by mistake and will be hard to see why it is failing.

function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) {
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken, ignoreDiagnostics?: boolean) {
// Ensure we have all the type information in place for this file so that all the
// emitter questions of this resolver will return the right information.
getDiagnostics(sourceFile, cancellationToken);
if (!ignoreDiagnostics) {
getDiagnostics(sourceFile, cancellationToken);
}
return emitResolver;
}

Expand Down
220 changes: 4 additions & 216 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,9 @@ namespace ts {
/** Returns its argument. */
export function identity<T>(x: T) { return x; }

/** Returns lower case string */
export function toLowerCase(x: string) { return x.toLowerCase(); }

/** Throws an error because a function is not implemented. */
export function notImplemented(): never {
throw new Error("Not implemented");
Expand Down Expand Up @@ -2931,9 +2934,7 @@ namespace ts {

export type GetCanonicalFileName = (fileName: string) => string;
export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName {
return useCaseSensitiveFileNames
? ((fileName) => fileName)
: ((fileName) => fileName.toLowerCase());
return useCaseSensitiveFileNames ? identity : toLowerCase;
}

/**
Expand Down Expand Up @@ -3058,223 +3059,10 @@ namespace ts {

export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty

export interface FileAndDirectoryExistence {
fileExists: boolean;
directoryExists: boolean;
}

export interface CachedDirectoryStructureHost extends DirectoryStructureHost {
/** Returns the queried result for the file exists and directory exists if at all it was done */
addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined;
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
clearCache(): void;
}

interface MutableFileSystemEntries {
readonly files: string[];
readonly directories: string[];
}

export const emptyFileSystemEntries: FileSystemEntries = {
files: emptyArray,
directories: emptyArray
};
export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost {
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>();
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
return {
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
newLine: host.newLine,
readFile: (path, encoding) => host.readFile(path, encoding),
write: s => host.write(s),
writeFile,
fileExists,
directoryExists,
createDirectory,
getCurrentDirectory,
getDirectories,
readDirectory,
addOrDeleteFileOrDirectory,
addOrDeleteFile,
clearCache,
exit: code => host.exit(code)
};

function toPath(fileName: string) {
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
}

function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined {
return cachedReadDirectoryResult.get(rootDirPath);
}

function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined {
return getCachedFileSystemEntries(getDirectoryPath(path));
}

function getBaseNameOfFileName(fileName: string) {
return getBaseFileName(normalizePath(fileName));
}

function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) {
const resultFromHost: MutableFileSystemEntries = {
files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [],
directories: host.getDirectories(rootDir) || []
};

cachedReadDirectoryResult.set(rootDirPath, resultFromHost);
return resultFromHost;
}

/**
* If the readDirectory result was already cached, it returns that
* Otherwise gets result from host and caches it.
* The host request is done under try catch block to avoid caching incorrect result
*/
function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined {
const cachedResult = getCachedFileSystemEntries(rootDirPath);
if (cachedResult) {
return cachedResult;
}

try {
return createCachedFileSystemEntries(rootDir, rootDirPath);
}
catch (_e) {
// If there is exception to read directories, dont cache the result and direct the calls to host
Debug.assert(!cachedReadDirectoryResult.has(rootDirPath));
return undefined;
}
}

function fileNameEqual(name1: string, name2: string) {
return getCanonicalFileName(name1) === getCanonicalFileName(name2);
}

function hasEntry(entries: ReadonlyArray<string>, name: string) {
return some(entries, file => fileNameEqual(file, name));
}

function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) {
if (hasEntry(entries, baseName)) {
if (!isValid) {
return filterMutate(entries, entry => !fileNameEqual(entry, baseName));
}
}
else if (isValid) {
return entries.push(baseName);
}
}

function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
const path = toPath(fileName);
const result = getCachedFileSystemEntriesForBaseDir(path);
if (result) {
updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true);
}
return host.writeFile(fileName, data, writeByteOrderMark);
}

function fileExists(fileName: string): boolean {
const path = toPath(fileName);
const result = getCachedFileSystemEntriesForBaseDir(path);
return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) ||
host.fileExists(fileName);
}

function directoryExists(dirPath: string): boolean {
const path = toPath(dirPath);
return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath);
}

function createDirectory(dirPath: string) {
const path = toPath(dirPath);
const result = getCachedFileSystemEntriesForBaseDir(path);
const baseFileName = getBaseNameOfFileName(dirPath);
if (result) {
updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true);
}
host.createDirectory(dirPath);
}

function getDirectories(rootDir: string): string[] {
const rootDirPath = toPath(rootDir);
const result = tryReadDirectory(rootDir, rootDirPath);
if (result) {
return result.directories.slice();
}
return host.getDirectories(rootDir);
}

function readDirectory(rootDir: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
const rootDirPath = toPath(rootDir);
const result = tryReadDirectory(rootDir, rootDirPath);
if (result) {
return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries);
}
return host.readDirectory(rootDir, extensions, excludes, includes, depth);

function getFileSystemEntries(dir: string) {
const path = toPath(dir);
if (path === rootDirPath) {
return result;
}
return tryReadDirectory(dir, path) || emptyFileSystemEntries;
}
}

function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
if (existingResult) {
// Just clear the cache for now
// For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated
clearCache();
}
else {
// This was earlier a file (hence not in cached directory contents)
// or we never cached the directory containing it
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath);
if (parentResult) {
const baseName = getBaseNameOfFileName(fileOrDirectory);
if (parentResult) {
const fsQueryResult: FileAndDirectoryExistence = {
fileExists: host.fileExists(fileOrDirectoryPath),
directoryExists: host.directoryExists(fileOrDirectoryPath)
};
if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) {
// Folder added or removed, clear the cache instead of updating the folder and its structure
clearCache();
}
else {
// No need to update the directory structure, just files
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists);
}
return fsQueryResult;
}
}
}
}

function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) {
if (eventKind === FileWatcherEventKind.Changed) {
return;
}

const parentResult = getCachedFileSystemEntriesForBaseDir(filePath);
if (parentResult) {
updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created);
}
}

function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) {
updateFileSystemEntry(parentResult.files, baseName, fileExists);
}

function clearCache() {
cachedReadDirectoryResult.clear();
}
}

export function singleElementArray<T>(t: T | undefined): T[] | undefined {
return t === undefined ? undefined : [t];
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2000,7 +2000,7 @@ namespace ts {
export function writeDeclarationFile(declarationFilePath: string, sourceFileOrBundle: SourceFile | Bundle, host: EmitHost, resolver: EmitResolver, emitterDiagnostics: DiagnosticCollection, emitOnlyDtsFiles: boolean) {
const emitDeclarationResult = emitDeclarations(host, resolver, emitterDiagnostics, declarationFilePath, sourceFileOrBundle, emitOnlyDtsFiles);
const emitSkipped = emitDeclarationResult.reportedDeclarationError || host.isEmitBlocked(declarationFilePath) || host.getCompilerOptions().noEmit;
if (!emitSkipped) {
if (!emitSkipped || emitOnlyDtsFiles) {
const sourceFiles = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle.sourceFiles : [sourceFileOrBundle];
const declarationOutput = emitDeclarationResult.referencesOutput
+ getDeclarationOutput(emitDeclarationResult.synchronousDeclarationOutput, emitDeclarationResult.moduleElementDeclarationEmitInfo);
Expand Down
53 changes: 27 additions & 26 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/// <reference path="sys.ts" />
/// <reference path="emitter.ts" />
/// <reference path="core.ts" />
/// <reference path="builder.ts" />

namespace ts {
const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/;
Expand Down Expand Up @@ -1141,32 +1140,34 @@ namespace ts {
function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult {
let declarationDiagnostics: ReadonlyArray<Diagnostic> = [];

if (options.noEmit) {
return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true };
}

// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
// get any preEmit diagnostics, not just the ones
if (options.noEmitOnError) {
const diagnostics = [
...program.getOptionsDiagnostics(cancellationToken),
...program.getSyntacticDiagnostics(sourceFile, cancellationToken),
...program.getGlobalDiagnostics(cancellationToken),
...program.getSemanticDiagnostics(sourceFile, cancellationToken)
];

if (diagnostics.length === 0 && program.getCompilerOptions().declaration) {
declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken);
if (!emitOnlyDtsFiles) {
if (options.noEmit) {
return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true };
}

if (diagnostics.length > 0 || declarationDiagnostics.length > 0) {
return {
diagnostics: concatenate(diagnostics, declarationDiagnostics),
sourceMaps: undefined,
emittedFiles: undefined,
emitSkipped: true
};
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
// get any preEmit diagnostics, not just the ones
if (options.noEmitOnError) {
const diagnostics = [
...program.getOptionsDiagnostics(cancellationToken),
...program.getSyntacticDiagnostics(sourceFile, cancellationToken),
...program.getGlobalDiagnostics(cancellationToken),
...program.getSemanticDiagnostics(sourceFile, cancellationToken)
];

if (diagnostics.length === 0 && program.getCompilerOptions().declaration) {
declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken);
}

if (diagnostics.length > 0 || declarationDiagnostics.length > 0) {
return {
diagnostics: concatenate(diagnostics, declarationDiagnostics),
sourceMaps: undefined,
emittedFiles: undefined,
emitSkipped: true
};
}
}
}

Expand All @@ -1178,7 +1179,7 @@ namespace ts {
// This is because in the -out scenario all files need to be emitted, and therefore all
// files need to be type checked. And the way to specify that all files need to be type
// checked is to not pass the file to getEmitResolver.
const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile);
const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken, emitOnlyDtsFiles);

performance.mark("beforeEmit");

Expand Down
Loading