Skip to content

Commit cc6d18e

Browse files
authored
Merge pull request #20234 from Microsoft/builderApi
Api for creating program in watch mode and using builder to get incremental emit/semantic diagnostics
2 parents d4c3612 + 8a51cda commit cc6d18e

28 files changed

+2235
-1207
lines changed

Jakefile.js

+3-14
Original file line numberDiff line numberDiff line change
@@ -1199,23 +1199,12 @@ task("update-sublime", ["local", serverFile], function () {
11991199
});
12001200

12011201
var tslintRuleDir = "scripts/tslint/rules";
1202-
var tslintRules = [
1203-
"booleanTriviaRule",
1204-
"debugAssertRule",
1205-
"nextLineRule",
1206-
"noBomRule",
1207-
"noDoubleSpaceRule",
1208-
"noIncrementDecrementRule",
1209-
"noInOperatorRule",
1210-
"noTypeAssertionWhitespaceRule",
1211-
"objectLiteralSurroundingSpaceRule",
1212-
"typeOperatorSpacingRule",
1213-
];
1202+
var tslintRules = fs.readdirSync(tslintRuleDir);
12141203
var tslintRulesFiles = tslintRules.map(function (p) {
1215-
return path.join(tslintRuleDir, p + ".ts");
1204+
return path.join(tslintRuleDir, p);
12161205
});
12171206
var tslintRulesOutFiles = tslintRules.map(function (p) {
1218-
return path.join(builtLocalDirectory, "tslint/rules", p + ".js");
1207+
return path.join(builtLocalDirectory, "tslint/rules", p.replace(".ts", ".js"));
12191208
});
12201209
var tslintFormattersDir = "scripts/tslint/formatters";
12211210
var tslintFormatters = [

src/compiler/builder.ts

+492-422
Large diffs are not rendered by default.

src/compiler/builderState.ts

+384
Large diffs are not rendered by default.

src/compiler/checker.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -749,10 +749,12 @@ namespace ts {
749749
return _jsxNamespace;
750750
}
751751

752-
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) {
752+
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken, ignoreDiagnostics?: boolean) {
753753
// Ensure we have all the type information in place for this file so that all the
754754
// emitter questions of this resolver will return the right information.
755-
getDiagnostics(sourceFile, cancellationToken);
755+
if (!ignoreDiagnostics) {
756+
getDiagnostics(sourceFile, cancellationToken);
757+
}
756758
return emitResolver;
757759
}
758760

src/compiler/core.ts

+4-216
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,9 @@ namespace ts {
14611461
/** Returns its argument. */
14621462
export function identity<T>(x: T) { return x; }
14631463

1464+
/** Returns lower case string */
1465+
export function toLowerCase(x: string) { return x.toLowerCase(); }
1466+
14641467
/** Throws an error because a function is not implemented. */
14651468
export function notImplemented(): never {
14661469
throw new Error("Not implemented");
@@ -2931,9 +2934,7 @@ namespace ts {
29312934

29322935
export type GetCanonicalFileName = (fileName: string) => string;
29332936
export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName {
2934-
return useCaseSensitiveFileNames
2935-
? ((fileName) => fileName)
2936-
: ((fileName) => fileName.toLowerCase());
2937+
return useCaseSensitiveFileNames ? identity : toLowerCase;
29372938
}
29382939

29392940
/**
@@ -3058,223 +3059,10 @@ namespace ts {
30583059

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

3061-
export interface FileAndDirectoryExistence {
3062-
fileExists: boolean;
3063-
directoryExists: boolean;
3064-
}
3065-
3066-
export interface CachedDirectoryStructureHost extends DirectoryStructureHost {
3067-
/** Returns the queried result for the file exists and directory exists if at all it was done */
3068-
addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined;
3069-
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
3070-
clearCache(): void;
3071-
}
3072-
3073-
interface MutableFileSystemEntries {
3074-
readonly files: string[];
3075-
readonly directories: string[];
3076-
}
3077-
30783062
export const emptyFileSystemEntries: FileSystemEntries = {
30793063
files: emptyArray,
30803064
directories: emptyArray
30813065
};
3082-
export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost {
3083-
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>();
3084-
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
3085-
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
3086-
return {
3087-
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
3088-
newLine: host.newLine,
3089-
readFile: (path, encoding) => host.readFile(path, encoding),
3090-
write: s => host.write(s),
3091-
writeFile,
3092-
fileExists,
3093-
directoryExists,
3094-
createDirectory,
3095-
getCurrentDirectory,
3096-
getDirectories,
3097-
readDirectory,
3098-
addOrDeleteFileOrDirectory,
3099-
addOrDeleteFile,
3100-
clearCache,
3101-
exit: code => host.exit(code)
3102-
};
3103-
3104-
function toPath(fileName: string) {
3105-
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
3106-
}
3107-
3108-
function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined {
3109-
return cachedReadDirectoryResult.get(rootDirPath);
3110-
}
3111-
3112-
function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined {
3113-
return getCachedFileSystemEntries(getDirectoryPath(path));
3114-
}
3115-
3116-
function getBaseNameOfFileName(fileName: string) {
3117-
return getBaseFileName(normalizePath(fileName));
3118-
}
3119-
3120-
function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) {
3121-
const resultFromHost: MutableFileSystemEntries = {
3122-
files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [],
3123-
directories: host.getDirectories(rootDir) || []
3124-
};
3125-
3126-
cachedReadDirectoryResult.set(rootDirPath, resultFromHost);
3127-
return resultFromHost;
3128-
}
3129-
3130-
/**
3131-
* If the readDirectory result was already cached, it returns that
3132-
* Otherwise gets result from host and caches it.
3133-
* The host request is done under try catch block to avoid caching incorrect result
3134-
*/
3135-
function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined {
3136-
const cachedResult = getCachedFileSystemEntries(rootDirPath);
3137-
if (cachedResult) {
3138-
return cachedResult;
3139-
}
3140-
3141-
try {
3142-
return createCachedFileSystemEntries(rootDir, rootDirPath);
3143-
}
3144-
catch (_e) {
3145-
// If there is exception to read directories, dont cache the result and direct the calls to host
3146-
Debug.assert(!cachedReadDirectoryResult.has(rootDirPath));
3147-
return undefined;
3148-
}
3149-
}
3150-
3151-
function fileNameEqual(name1: string, name2: string) {
3152-
return getCanonicalFileName(name1) === getCanonicalFileName(name2);
3153-
}
3154-
3155-
function hasEntry(entries: ReadonlyArray<string>, name: string) {
3156-
return some(entries, file => fileNameEqual(file, name));
3157-
}
3158-
3159-
function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) {
3160-
if (hasEntry(entries, baseName)) {
3161-
if (!isValid) {
3162-
return filterMutate(entries, entry => !fileNameEqual(entry, baseName));
3163-
}
3164-
}
3165-
else if (isValid) {
3166-
return entries.push(baseName);
3167-
}
3168-
}
3169-
3170-
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
3171-
const path = toPath(fileName);
3172-
const result = getCachedFileSystemEntriesForBaseDir(path);
3173-
if (result) {
3174-
updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true);
3175-
}
3176-
return host.writeFile(fileName, data, writeByteOrderMark);
3177-
}
3178-
3179-
function fileExists(fileName: string): boolean {
3180-
const path = toPath(fileName);
3181-
const result = getCachedFileSystemEntriesForBaseDir(path);
3182-
return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) ||
3183-
host.fileExists(fileName);
3184-
}
3185-
3186-
function directoryExists(dirPath: string): boolean {
3187-
const path = toPath(dirPath);
3188-
return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath);
3189-
}
3190-
3191-
function createDirectory(dirPath: string) {
3192-
const path = toPath(dirPath);
3193-
const result = getCachedFileSystemEntriesForBaseDir(path);
3194-
const baseFileName = getBaseNameOfFileName(dirPath);
3195-
if (result) {
3196-
updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true);
3197-
}
3198-
host.createDirectory(dirPath);
3199-
}
3200-
3201-
function getDirectories(rootDir: string): string[] {
3202-
const rootDirPath = toPath(rootDir);
3203-
const result = tryReadDirectory(rootDir, rootDirPath);
3204-
if (result) {
3205-
return result.directories.slice();
3206-
}
3207-
return host.getDirectories(rootDir);
3208-
}
3209-
3210-
function readDirectory(rootDir: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
3211-
const rootDirPath = toPath(rootDir);
3212-
const result = tryReadDirectory(rootDir, rootDirPath);
3213-
if (result) {
3214-
return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries);
3215-
}
3216-
return host.readDirectory(rootDir, extensions, excludes, includes, depth);
3217-
3218-
function getFileSystemEntries(dir: string) {
3219-
const path = toPath(dir);
3220-
if (path === rootDirPath) {
3221-
return result;
3222-
}
3223-
return tryReadDirectory(dir, path) || emptyFileSystemEntries;
3224-
}
3225-
}
3226-
3227-
function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
3228-
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
3229-
if (existingResult) {
3230-
// Just clear the cache for now
3231-
// For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated
3232-
clearCache();
3233-
}
3234-
else {
3235-
// This was earlier a file (hence not in cached directory contents)
3236-
// or we never cached the directory containing it
3237-
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath);
3238-
if (parentResult) {
3239-
const baseName = getBaseNameOfFileName(fileOrDirectory);
3240-
if (parentResult) {
3241-
const fsQueryResult: FileAndDirectoryExistence = {
3242-
fileExists: host.fileExists(fileOrDirectoryPath),
3243-
directoryExists: host.directoryExists(fileOrDirectoryPath)
3244-
};
3245-
if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) {
3246-
// Folder added or removed, clear the cache instead of updating the folder and its structure
3247-
clearCache();
3248-
}
3249-
else {
3250-
// No need to update the directory structure, just files
3251-
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists);
3252-
}
3253-
return fsQueryResult;
3254-
}
3255-
}
3256-
}
3257-
}
3258-
3259-
function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) {
3260-
if (eventKind === FileWatcherEventKind.Changed) {
3261-
return;
3262-
}
3263-
3264-
const parentResult = getCachedFileSystemEntriesForBaseDir(filePath);
3265-
if (parentResult) {
3266-
updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created);
3267-
}
3268-
}
3269-
3270-
function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) {
3271-
updateFileSystemEntry(parentResult.files, baseName, fileExists);
3272-
}
3273-
3274-
function clearCache() {
3275-
cachedReadDirectoryResult.clear();
3276-
}
3277-
}
32783066

32793067
export function singleElementArray<T>(t: T | undefined): T[] | undefined {
32803068
return t === undefined ? undefined : [t];

src/compiler/declarationEmitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2000,7 +2000,7 @@ namespace ts {
20002000
export function writeDeclarationFile(declarationFilePath: string, sourceFileOrBundle: SourceFile | Bundle, host: EmitHost, resolver: EmitResolver, emitterDiagnostics: DiagnosticCollection, emitOnlyDtsFiles: boolean) {
20012001
const emitDeclarationResult = emitDeclarations(host, resolver, emitterDiagnostics, declarationFilePath, sourceFileOrBundle, emitOnlyDtsFiles);
20022002
const emitSkipped = emitDeclarationResult.reportedDeclarationError || host.isEmitBlocked(declarationFilePath) || host.getCompilerOptions().noEmit;
2003-
if (!emitSkipped) {
2003+
if (!emitSkipped || emitOnlyDtsFiles) {
20042004
const sourceFiles = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle.sourceFiles : [sourceFileOrBundle];
20052005
const declarationOutput = emitDeclarationResult.referencesOutput
20062006
+ getDeclarationOutput(emitDeclarationResult.synchronousDeclarationOutput, emitDeclarationResult.moduleElementDeclarationEmitInfo);

src/compiler/program.ts

+27-26
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/// <reference path="sys.ts" />
22
/// <reference path="emitter.ts" />
33
/// <reference path="core.ts" />
4-
/// <reference path="builder.ts" />
54

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

1144-
if (options.noEmit) {
1145-
return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true };
1146-
}
1147-
1148-
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
1149-
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
1150-
// get any preEmit diagnostics, not just the ones
1151-
if (options.noEmitOnError) {
1152-
const diagnostics = [
1153-
...program.getOptionsDiagnostics(cancellationToken),
1154-
...program.getSyntacticDiagnostics(sourceFile, cancellationToken),
1155-
...program.getGlobalDiagnostics(cancellationToken),
1156-
...program.getSemanticDiagnostics(sourceFile, cancellationToken)
1157-
];
1158-
1159-
if (diagnostics.length === 0 && program.getCompilerOptions().declaration) {
1160-
declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken);
1143+
if (!emitOnlyDtsFiles) {
1144+
if (options.noEmit) {
1145+
return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true };
11611146
}
11621147

1163-
if (diagnostics.length > 0 || declarationDiagnostics.length > 0) {
1164-
return {
1165-
diagnostics: concatenate(diagnostics, declarationDiagnostics),
1166-
sourceMaps: undefined,
1167-
emittedFiles: undefined,
1168-
emitSkipped: true
1169-
};
1148+
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
1149+
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
1150+
// get any preEmit diagnostics, not just the ones
1151+
if (options.noEmitOnError) {
1152+
const diagnostics = [
1153+
...program.getOptionsDiagnostics(cancellationToken),
1154+
...program.getSyntacticDiagnostics(sourceFile, cancellationToken),
1155+
...program.getGlobalDiagnostics(cancellationToken),
1156+
...program.getSemanticDiagnostics(sourceFile, cancellationToken)
1157+
];
1158+
1159+
if (diagnostics.length === 0 && program.getCompilerOptions().declaration) {
1160+
declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken);
1161+
}
1162+
1163+
if (diagnostics.length > 0 || declarationDiagnostics.length > 0) {
1164+
return {
1165+
diagnostics: concatenate(diagnostics, declarationDiagnostics),
1166+
sourceMaps: undefined,
1167+
emittedFiles: undefined,
1168+
emitSkipped: true
1169+
};
1170+
}
11701171
}
11711172
}
11721173

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

11831184
performance.mark("beforeEmit");
11841185

0 commit comments

Comments
 (0)