Project references WIP

This commit is contained in:
Ryan Cavanaugh
2017-12-15 15:38:32 -08:00
parent 538f1bf4f4
commit 589602529d
18 changed files with 487 additions and 11 deletions
+1
View File
@@ -131,6 +131,7 @@ var harnessSources = harnessCoreSources.concat([
"tsconfigParsing.ts",
"builder.ts",
"commandLineParsing.ts",
"projectReferences.ts",
"configurationExtension.ts",
"convertCompilerOptionsFromJson.ts",
"convertTypeAcquisitionFromJson.ts",
+29 -1
View File
@@ -384,6 +384,17 @@ namespace ts {
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Type_declaration_files_to_be_included_in_compilation
},
{
name: "references",
type: "list",
element: {
name: "references",
type: "object"
},
showInSimplifiedHelpView: true,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Projects_to_reference
},
{
name: "allowSyntheticDefaultImports",
type: "boolean",
@@ -902,8 +913,9 @@ namespace ts {
*/
export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } {
const jsonSourceFile = parseJsonText(fileName, jsonText);
const config = convertToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics);
return {
config: convertToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics),
config,
error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined
};
}
@@ -966,6 +978,22 @@ namespace ts {
type: "string"
}
},
{
name: "references",
type: "list",
element: {
name: "references",
type: "object"
}
},
{
name: "projects",
type: "list",
element: {
name: "projects",
type: "string"
}
},
{
name: "include",
type: "list",
+20
View File
@@ -3326,6 +3326,26 @@
"category": "Message",
"code": 6186
},
"Project references may not form a circular graph. Cycle detected: {0}": {
"category": "Error",
"code": 6187
},
"Projects to reference": {
"category": "Message",
"code": 6188
},
"Referenced project '{0}' must have 'declaration': true": {
"category": "Error",
"code": 6201
},
"Referenced project '{0}' must have an explicit 'rootDir' setting": {
"category": "Error",
"code": 6202
},
"Output file '{0}' has not been built from source file '{1}'": {
"category": "Error",
"code": 6203
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
+138 -7
View File
@@ -333,12 +333,12 @@ namespace ts {
}
output += host.getNewLine();
output += `${ relativeFileName }(${ firstLine + 1 },${ firstLineChar + 1 }): `;
output += `${relativeFileName}(${firstLine + 1},${firstLineChar + 1}): `;
}
const categoryColor = getCategoryFormat(diagnostic.category);
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
output += `${ formatAndReset(category, categoryColor) } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()) }`;
output += `${formatAndReset(category, categoryColor)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}`;
if (diagnostic.file) {
output += host.getNewLine();
@@ -562,6 +562,10 @@ namespace ts {
resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile) => loadWithLocalCache(checkAllDefined(typeReferenceDirectiveNames), containingFile, loader);
}
const projectReferenceRedirects = createProjectReferenceRedirects(options);
checkProjectReferenceGraph();
void getReferencesSyntax;
// Map from a stringified PackageId to the source file with that id.
// Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile).
// `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around.
@@ -1098,7 +1102,7 @@ namespace ts {
// If '--lib' is not specified, include default library file according to '--target'
// otherwise, using options specified in '--lib' instead of '--target' default library file
if (!options.lib) {
return compareStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo;
return compareStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo;
}
else {
return forEach(options.lib, libFileName => compareStrings(file.fileName, combinePaths(defaultLibraryPath, libFileName), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo);
@@ -1695,7 +1699,13 @@ namespace ts {
const sourceFile = getSourceFile(fileName);
if (fail) {
if (!sourceFile) {
fail(Diagnostics.File_0_not_found, fileName);
const redirect = getProjectReferenceRedirect(fileName);
if (redirect) {
fail(Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName);
}
else {
fail(Diagnostics.File_0_not_found, fileName);
}
}
else if (refFile && host.getCanonicalFileName(fileName) === host.getCanonicalFileName(refFile.fileName)) {
fail(Diagnostics.A_file_cannot_have_a_reference_to_itself);
@@ -1791,6 +1801,9 @@ namespace ts {
return file;
}
const redirect = getProjectReferenceRedirect(fileName);
fileName = redirect || fileName;
// We haven't looked for this file, do so now and cache result
const file = host.getSourceFile(fileName, options.target, hostErrorMessage => {
if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) {
@@ -1860,6 +1873,23 @@ namespace ts {
return file;
}
function getProjectReferenceRedirect(fileName: string): string | undefined {
const path = toPath(fileName);
// If this file is produced by a referenced project, we need to rewrite it to
// look in the output folder of the referenced project rather than the input
const normalized = getNormalizedAbsolutePath(fileName, path);
let result: string | undefined = undefined;
projectReferenceRedirects.forEach((v, k) => {
if (result !== undefined) {
return undefined;
}
if (normalized.indexOf(k) === 0) {
result = changeExtension(fileName.replace(k, v), ".d.ts");
}
});
return result;
}
function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) {
forEach(file.referencedFiles, ref => {
const referencedFileName = resolveTripleslashReference(ref.fileName, file.fileName);
@@ -2032,6 +2062,59 @@ namespace ts {
return allFilesBelongToPath;
}
function createProjectReferenceRedirects(rootOptions: CompilerOptions): Map<string> {
const result = createMap<string>();
walkProjectReferenceGraph(host, rootOptions, createMapping);
function createMapping(_resolvedFile: string, referencedProject: CompilerOptions) {
// No rootDir in target set; this will be an error later on in the process
if (referencedProject.rootDir === undefined) return;
result.set(referencedProject.rootDir, referencedProject.outDir);
// If this project uses outFile, add the outFile to our compilation
if (referencedProject.outFile) {
const outFile = combinePaths(referencedProject.outDir, referencedProject.outFile);
processSourceFile(outFile, /*isDefaultLib*/ false, /*packageId*/ undefined);
}
}
return result;
}
function checkProjectReferenceGraph() {
// Checks the following conditions:
// * Any referenced project has declaration: true
// * Any referenced project has an explicit rootDir
// * No circularities exist
// * TODO No project root is a subfolder of any other project root
const illegalRefs = createMap<true>();
const cycleName: string[] = [options.configFilePath || host.getCurrentDirectory()];
walkProjectReferenceGraph(host, options, checkReference, createDiagnosticForOptionName);
function checkReference(fileName: string, opts: CompilerOptions) {
const normalizedPath = ts.normalizePath(fileName);
if (illegalRefs.has(normalizedPath)) {
createDiagnosticForOptionName(Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, cycleName.map(normalizePath).map(s => host.getNewLine() + " " + s).join(" -> "));
return;
}
if (opts === undefined) {
Debug.fail("Options cannot be undefined");
return;
}
if (!opts.declaration) {
createDiagnosticForOptionName(Diagnostics.Referenced_project_0_must_have_declaration_Colon_true, fileName);
}
if (!opts.rootDir) {
createDiagnosticForOptionName(Diagnostics.Referenced_project_0_must_have_an_explicit_rootDir_setting, fileName);
}
illegalRefs.set(normalizedPath, true);
cycleName.push(normalizedPath);
walkProjectReferenceGraph(host, opts, checkReference, createDiagnosticForOptionName);
cycleName.pop();
illegalRefs.delete(normalizedPath);
}
}
function verifyCompilerOptions() {
if (options.isolatedModules) {
if (options.declaration) {
@@ -2277,12 +2360,20 @@ namespace ts {
}
}
function getOptionPathsSyntax() {
function getOptionsSyntaxByName(name: string): object | undefined {
const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax();
if (compilerOptionsObjectLiteralSyntax) {
return getPropertyAssignment(compilerOptionsObjectLiteralSyntax, "paths");
return getPropertyAssignment(compilerOptionsObjectLiteralSyntax, name);
}
return emptyArray;
return undefined;
}
function getReferencesSyntax(): ObjectLiteralExpression[] | undefined {
return getOptionsSyntaxByName("references") as ObjectLiteralExpression[] | undefined;
}
function getOptionPathsSyntax(): PropertyAssignment[] {
return getOptionsSyntaxByName("paths") as PropertyAssignment[] || emptyArray;
}
function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string) {
@@ -2332,6 +2423,46 @@ namespace ts {
}
}
function parseConfigHostFromCompilerHost(host: CompilerHost): ParseConfigHost {
return {
fileExists: host.fileExists,
readDirectory: () => [],
readFile: host.readFile,
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames()
};
}
export function walkProjectReferenceGraph(host: CompilerHost, rootOptions: CompilerOptions,
callback: (resolvedFile: string, referencedProject: CompilerOptions) => void,
error?: (message: DiagnosticMessage | DiagnosticMessageChain | string, option1?: string) => void) {
if (rootOptions.references === undefined) return;
const configHost = parseConfigHostFromCompilerHost(host);
const rootPath = rootOptions.configFilePath ? getDirectoryPath(rootOptions.configFilePath) : host.getCurrentDirectory();
for (const ref of rootOptions.references) {
let refPath = combinePaths(rootPath, ref.path);
if (!host.fileExists(refPath)) {
refPath = combinePaths(refPath, "tsconfig.json");
}
if (!host.fileExists(refPath)) {
if (error) {
error(Diagnostics.File_0_not_found, refPath);
}
continue;
}
const referenceJsonSource = parseJsonText(refPath, host.readFile(refPath));
const cmdLine = parseJsonSourceFileConfigFileContent(referenceJsonSource, configHost, getDirectoryPath(refPath), /*existingOptions*/ undefined, refPath);
cmdLine.options.configFilePath = refPath;
if (cmdLine.errors && cmdLine.errors.length) {
// TODO: Pass along errors
}
if (cmdLine.options) {
callback(refPath, cmdLine.options);
}
}
}
/* @internal */
/**
* Returns a DiagnosticMessage if we won't include a resolved module due to its extension.
+6 -1
View File
@@ -3675,7 +3675,11 @@ namespace ts {
name: string;
}
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | null | undefined;
export interface ProjectReference {
path: string;
}
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
export interface CompilerOptions {
/*@internal*/ all?: boolean;
@@ -3743,6 +3747,7 @@ namespace ts {
/* @internal */ pretty?: DiagnosticStyle;
reactNamespace?: string;
jsxFactory?: string;
references?: ProjectReference[];
removeComments?: boolean;
rootDir?: string;
rootDirs?: string[];
+208
View File
@@ -0,0 +1,208 @@
/// <reference path="../harness.ts" />
/// <reference path="../../compiler/commandLineParser.ts" />
namespace ts {
interface TestProjectSpecification {
configFileName?: string;
references: string[];
files: { [fileName: string]: string };
outputFiles?: { [fileName: string]: string };
options?: Partial<CompilerOptions>;
}
interface TestSpecification {
[path: string]: TestProjectSpecification;
}
function assertHasError(message: string, errors: ReadonlyArray<Diagnostic>, diag: DiagnosticMessage) {
if (!errors.some(e => e.code === diag.code)) {
const errorString = errors.map(e => ` ${e.file ? e.file.fileName : "[global]"}: ${e.messageText}`).join("\r\n");
assert(false, `${message}: Did not find any diagnostic for ${diag.message} in:\r\n${errorString}`);
}
}
function assertNoErrors(message: string, errors: ReadonlyArray<Diagnostic>) {
if (errors && errors.length > 0) {
assert(false, `${message}: Expected no errors, but found:\r\n${errors.map(e => ` ${e.messageText}`).join("\r\n")}`);
}
}
function combineAllPaths(...paths: string[]) {
let result = paths[0];
for (let i = 1; i < paths.length; i++) {
result = combinePaths(result, paths[i]);
}
return result;
}
const emptyModule = "export { };";
/**
* Produces the text of a source file which imports all of the
* specified module names
*/
function moduleImporting(...names: string[]) {
return names.map((n, i) => `import * as mod_${i} from ${n}`).join("\r\n");
}
function testProjectReferences(spec: TestSpecification, entryPointConfigFileName: string, checkResult: (prog: Program) => void) {
const files = createMap<string>();
for (const key in spec) {
const sp = spec[key];
const configFileName = combineAllPaths("/", key, sp.configFileName || "tsconfig.json");
const options = {
compilerOptions: {
references: sp.references.map(r => ({ path: r })),
declaration: true,
rootDir: ".",
outDir: "bin",
...sp.options
}
};
const configContent = JSON.stringify(options);
const outDir = options.compilerOptions.outDir;
files.set(configFileName, configContent);
for (const sourceFile of Object.keys(sp.files)) {
files.set(sourceFile, sp.files[sourceFile]);
}
if (sp.outputFiles) {
for (const outFile of Object.keys(sp.outputFiles)) {
files.set(combineAllPaths("/", key, outDir, outFile), sp.outputFiles[outFile]);
}
}
}
const host = new Utils.MockProjectReferenceCompilerHost("/", /*useCaseSensitiveFileNames*/ true, files);
const { config, error } = ts.readConfigFile(entryPointConfigFileName, name => host.readFile(name));
// We shouldn't have any errors about invalid tsconfig files in these tests
assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
const file = ts.parseJsonConfigFileContent(config, host.configHost, getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName);
file.options.configFilePath = entryPointConfigFileName;
const prog = ts.createProgram(file.fileNames, file.options, host);
checkResult(prog);
}
describe("project-references meta check", () => {
it("default setup was created correctly", () => {
const spec: TestSpecification = {
"/primary": {
files: { "/primary/a.ts": emptyModule },
references: []
},
"/reference": {
files: { "/secondary/b.ts": moduleImporting("../primary/a") },
references: ["../primary"]
}
};
testProjectReferences(spec, "/primary/tsconfig.json", prog => {
assert.isTrue(!!prog, "Program should exist");
assertNoErrors("Sanity check should not produce errors", prog.getOptionsDiagnostics());
});
});
it("can detect a circularity error", () => {
const spec: TestSpecification = {
"/primary": {
files: { "/primary/a.ts": emptyModule },
references: ["../secondary"]
},
"/secondary": {
files: { "/secondary/b.ts": moduleImporting("../primary/a") },
references: ["../primary"]
}
};
testProjectReferences(spec, "/primary/tsconfig.json", prog => {
assert.isTrue(!!prog, "Program should exist");
assertHasError("Should detect a circular error", prog.getOptionsDiagnostics(), Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0);
});
});
});
/**
* Validate that we enforce the basic settings constraints for referenced projects
*/
describe("project-references constraint checking for settings", () => {
const spec: TestSpecification = {
"/primary": {
files: { "/primary/a.ts": emptyModule },
references: ["../secondary"]
},
"/secondary": {
files: { "/secondary/b.ts": moduleImporting("../primary/a") },
references: [],
options: {
declaration: false
}
}
};
it("errors when declaration = false", () => {
testProjectReferences(spec, "/primary/tsconfig.json", program => {
const errs = program.getOptionsDiagnostics();
assertHasError("Reports an error about the wrong decl setting", errs, Diagnostics.Referenced_project_0_must_have_declaration_Colon_true);
});
});
it("errors when rootDir is not set", () => {
spec["/secondary"].options.declaration = true;
spec["/secondary"].options.rootDir = undefined;
testProjectReferences(spec, "/primary/tsconfig.json", program => {
const errs = program.getOptionsDiagnostics();
assertHasError("Reports an error about the wrong decl setting", errs, Diagnostics.Referenced_project_0_must_have_an_explicit_rootDir_setting);
});
});
// * TODO No project root is a subfolder of any other project root
});
/**
* Circularity checking
*/
describe("project-references circularity checking", () => {
// Bare cycle with relative paths tested in sanity check block
it("detects an indirected cycle", () => {
const spec: TestSpecification = {
"/alpha": {
files: { "/alpha/a.ts": emptyModule },
references: ["../beta"]
},
"/beta": {
files: { "/beta/b.ts": moduleImporting("../alpha/a") },
references: ["../gamma"]
},
"/gamma": {
files: { "/gamma/a.ts": emptyModule },
references: ["../alpha"],
}
};
testProjectReferences(spec, "/alpha/tsconfig.json", program => {
const errs = program.getOptionsDiagnostics();
assertHasError("Reports an error about the circular diagnsotic", errs, Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0);
});
});
});
/**
* Path mapping behavior
*/
describe("project-references path mapping", () => {
it("redirects to the output .d.ts file", () => {
const spec: TestSpecification = {
"/alpha": {
files: { "/alpha/a.ts": "export const m: number;" },
references: [],
outputFiles: { "a.d.ts": emptyModule }
},
"/beta": {
files: { "/beta/b.ts": "import { m } from '../alpha/a'" },
references: ["../alpha"]
}
};
testProjectReferences(spec, "/beta/tsconfig.json", program => {
assertNoErrors("File setup should be correct", program.getOptionsDiagnostics());
assertHasError("Found a type error", program.getSemanticDiagnostics(), Diagnostics.Module_0_has_no_exported_member_1);
});
});
});
}
+63
View File
@@ -220,4 +220,67 @@ namespace Utils {
return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, (path: string) => this.getAccessibleFileSystemEntries(path));
}
}
export class MockProjectReferenceCompilerHost implements ts.CompilerHost {
public configHost: ts.ParseConfigHost = new MockParseConfigHost(this.currentDirectory, this.ignoreCase, this.files);
private readonly getCanonicalFileNameImpl = ts.createGetCanonicalFileName(!this.ignoreCase);
constructor(private currentDirectory: string, private ignoreCase: boolean, private files: ts.Map<string> | string[]) {
}
getCanonicalFileName = (fileName: string): string => {
return this.getCanonicalFileNameImpl(fileName);
}
fileExists = (fileName: string): boolean => {
return this.configHost.fileExists(fileName);
}
// TODO try deleting this
directoryExists = (dirName: string): boolean => {
const fullName = this.getCanonicalFileName(dirName);
let exists = false;
if (Array.isArray(this.files)) {
for (const k of this.files) {
if (this.getCanonicalFileName(k).indexOf(fullName) === 0) {
exists = true;
}
}
}
else {
this.files.forEach((_v, k) => {
if (this.getCanonicalFileName(k).indexOf(fullName) === 0) {
exists = true;
}
});
}
return exists;
}
readFile = (fileName: string): string => {
if (fileName === "lib.d.ts") return "declare var window: any;";
return this.configHost.readFile(fileName);
}
getSourceFile = (fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile => {
const content = this.readFile(fileName);
if (content === undefined) {
return undefined;
}
return ts.createSourceFile(fileName, content, languageVersion);
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return ts.getDefaultLibFileName(options);
}
writeFile: ts.WriteFileCallback;
getCurrentDirectory = (): string => {
return this.currentDirectory;
}
getNewLine(): string {
return "\r\n";
}
getDirectories(): string[] {
return [];
}
useCaseSensitiveFileNames = () => {
return this.ignoreCase;
}
}
}
+1
View File
@@ -2501,6 +2501,7 @@ namespace ts.server.protocol {
project?: string;
reactNamespace?: string;
removeComments?: boolean;
references?: ProjectReference[];
rootDir?: string;
rootDirs?: string[];
skipLibCheck?: boolean;
+7 -1
View File
@@ -2199,7 +2199,10 @@ declare namespace ts {
interface PluginImport {
name: string;
}
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | null | undefined;
interface ProjectReference {
path: string;
}
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
interface CompilerOptions {
allowJs?: boolean;
allowSyntheticDefaultImports?: boolean;
@@ -2252,6 +2255,7 @@ declare namespace ts {
project?: string;
reactNamespace?: string;
jsxFactory?: string;
references?: ProjectReference[];
removeComments?: boolean;
rootDir?: string;
rootDirs?: string[];
@@ -3766,6 +3770,7 @@ declare namespace ts {
* @returns A 'Program' object.
*/
function createProgram(rootNames: ReadonlyArray<string>, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program;
function walkProjectReferenceGraph(host: CompilerHost, rootOptions: CompilerOptions, callback: (resolvedFile: string, referencedProject: CompilerOptions) => void, error?: (message: DiagnosticMessage | DiagnosticMessageChain | string, option1?: string) => void): void;
}
declare namespace ts {
interface Node {
@@ -6788,6 +6793,7 @@ declare namespace ts.server.protocol {
project?: string;
reactNamespace?: string;
removeComments?: boolean;
references?: ProjectReference[];
rootDir?: string;
rootDirs?: string[];
skipLibCheck?: boolean;
+6 -1
View File
@@ -2199,7 +2199,10 @@ declare namespace ts {
interface PluginImport {
name: string;
}
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | null | undefined;
interface ProjectReference {
path: string;
}
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
interface CompilerOptions {
allowJs?: boolean;
allowSyntheticDefaultImports?: boolean;
@@ -2252,6 +2255,7 @@ declare namespace ts {
project?: string;
reactNamespace?: string;
jsxFactory?: string;
references?: ProjectReference[];
removeComments?: boolean;
rootDir?: string;
rootDirs?: string[];
@@ -3713,6 +3717,7 @@ declare namespace ts {
* @returns A 'Program' object.
*/
function createProgram(rootNames: ReadonlyArray<string>, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program;
function walkProjectReferenceGraph(host: CompilerHost, rootOptions: CompilerOptions, callback: (resolvedFile: string, referencedProject: CompilerOptions) => void, error?: (message: DiagnosticMessage | DiagnosticMessageChain | string, option1?: string) => void): void;
}
declare namespace ts {
function parseCommandLine(commandLine: ReadonlyArray<string>, readFile?: (path: string) => string | undefined): ParsedCommandLine;
@@ -39,6 +39,7 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "references": [], /* Projects to reference */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
@@ -39,6 +39,7 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "references": [], /* Projects to reference */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
@@ -39,6 +39,7 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "references": [], /* Projects to reference */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
@@ -39,6 +39,7 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "references": [], /* Projects to reference */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
@@ -39,6 +39,7 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "references": [], /* Projects to reference */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
@@ -39,6 +39,7 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "references": [], /* Projects to reference */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
@@ -39,6 +39,7 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "references": [], /* Projects to reference */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
@@ -39,6 +39,7 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
"types": ["jquery","mocha"] /* Type declaration files to be included in compilation. */
// "references": [], /* Projects to reference */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */