diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 8169421ca17..47f0594a76e 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -110,6 +110,13 @@ module ts { type: "boolean", description: Diagnostics.Do_not_emit_comments_to_output, }, + { + name: "rootDir", + type: "string", + isFilePath: true, + description: Diagnostics.Specifies_the_root_directory_of_input_files_Use_to_control_the_output_directory_structure_with_outDir, + paramType: Diagnostics.LOCATION, + }, { name: "separateCompilation", type: "boolean", diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 0271ac73105..7c9d9b56c63 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -495,6 +495,8 @@ module ts { Suppress_noImplicitAny_errors_for_indexing_objects_lacking_index_signatures: { code: 6055, category: DiagnosticCategory.Message, key: "Suppress noImplicitAny errors for indexing objects lacking index signatures." }, Do_not_emit_declarations_for_code_that_has_an_internal_annotation: { code: 6056, category: DiagnosticCategory.Message, key: "Do not emit declarations for code that has an '@internal' annotation." }, Preserve_new_lines_when_emitting_code: { code: 6057, category: DiagnosticCategory.Message, key: "Preserve new-lines when emitting code." }, + Specifies_the_root_directory_of_input_files_Use_to_control_the_output_directory_structure_with_outDir: { code: 6058, category: DiagnosticCategory.Message, key: "Specifies the root directory of input files. Use to control the output directory structure with --outDir." }, + File_0_is_not_under_rootDir_1_RootDir_is_expected_to_contain_all_source_files: { code: 6059, category: DiagnosticCategory.Error, key: "File '{0}' is not under rootDir '{1}'. RootDir is expected to contain all source files." }, Variable_0_implicitly_has_an_1_type: { code: 7005, category: DiagnosticCategory.Error, key: "Variable '{0}' implicitly has an '{1}' type." }, Parameter_0_implicitly_has_an_1_type: { code: 7006, category: DiagnosticCategory.Error, key: "Parameter '{0}' implicitly has an '{1}' type." }, Member_0_implicitly_has_an_1_type: { code: 7008, category: DiagnosticCategory.Error, key: "Member '{0}' implicitly has an '{1}' type." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 063a0d50ef5..31fc9090a6f 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1968,6 +1968,15 @@ "category": "Message", "code": 6057 }, + "Specifies the root directory of input files. Use to control the output directory structure with --outDir.": { + "category": "Message", + "code": 6058 + }, + "File '{0}' is not under rootDir '{1}'. RootDir is expected to contain all source files.": { + "category": "Error", + "code": 6059 + }, + "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 64090f76344..e3fe08d04eb 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -455,47 +455,62 @@ module ts { function computecommonSourceDirectory(sourceFiles: SourceFile[]): string { let commonPathComponents: string[]; + let currentDirectory = host.getCurrentDirectory(); forEach(files, sourceFile => { // Each file contributes into common source file path - if (!(sourceFile.flags & NodeFlags.DeclarationFile) - && !fileExtensionIs(sourceFile.fileName, ".js")) { - let sourcePathComponents = getNormalizedPathComponents(sourceFile.fileName, host.getCurrentDirectory()); - sourcePathComponents.pop(); // FileName is not part of directory - if (commonPathComponents) { - for (let i = 0; i < Math.min(commonPathComponents.length, sourcePathComponents.length); i++) { - if (commonPathComponents[i] !== sourcePathComponents[i]) { - if (i === 0) { - diagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files)); - return; - } + if (isDeclarationFile(sourceFile)) { + return; + } - // New common path found that is 0 -> i-1 - commonPathComponents.length = i; - break; + let sourcePathComponents = getNormalizedPathComponents(sourceFile.fileName, currentDirectory); + sourcePathComponents.pop(); // FileName is not part of directory + if (commonPathComponents) { + for (let i = 0; i < Math.min(commonPathComponents.length, sourcePathComponents.length); i++) { + if (commonPathComponents[i] !== sourcePathComponents[i]) { + if (i === 0) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files)); + return; } - } - // If the fileComponent path completely matched and less than already found update the length - if (sourcePathComponents.length < commonPathComponents.length) { - commonPathComponents.length = sourcePathComponents.length; + // New common path found that is 0 -> i-1 + commonPathComponents.length = i; + break; } } - else { - // first file - commonPathComponents = sourcePathComponents; + + // If the fileComponent path completely matched and less than already found update the length + if (sourcePathComponents.length < commonPathComponents.length) { + commonPathComponents.length = sourcePathComponents.length; } } + else { + // first file + commonPathComponents = sourcePathComponents; + } + }); - let commonSourceDirectory = getNormalizedPathFromPathComponents(commonPathComponents); - if (commonSourceDirectory) { - // Make sure directory path ends with directory separator so this string can directly - // used to replace with "" to get the relative path of the source file and the relative path doesn't - // start with / making it rooted path - commonSourceDirectory += directorySeparator; + return getNormalizedPathFromPathComponents(commonPathComponents); + } + + function checkSourceFilesBelongToPath(soruceFiles: SourceFile[], rootDirectory: string): boolean { + let allFilesBelongToPath = true; + if (files) { + let currentDirectory = host.getCurrentDirectory(); + let absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); + + for (var sourceFile of files) { + if (!isDeclarationFile(sourceFile)) { + let absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); + if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { + diagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_under_rootDir_1_RootDir_is_expected_to_contain_all_source_files, sourceFile.fileName, options.rootDir)); + allFilesBelongToPath = false; + } + } + } } - return commonSourceDirectory; + return allFilesBelongToPath; } function verifyCompilerOptions() { @@ -560,7 +575,21 @@ module ts { (options.mapRoot && // there is --mapRoot specified and there would be multiple js files generated (!options.out || firstExternalModuleSourceFile !== undefined))) { - commonSourceDirectory = computecommonSourceDirectory(files); + if (options.rootDir && checkSourceFilesBelongToPath(files, options.rootDir)) { + // If a rootDir is specified and is valid use it as the commonSourceDirectory + commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, host.getCurrentDirectory()); + } + else { + // Compute the commonSourceDirectory from the input files + commonSourceDirectory = computecommonSourceDirectory(files); + } + + if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { + // Make sure directory path ends with directory separator so this string can directly + // used to replace with "" to get the relative path of the source file and the relative path doesn't + // start with / making it rooted path + commonSourceDirectory += directorySeparator; + } } if (options.noEmit) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 43c210331a5..0e7c089cc27 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1648,6 +1648,7 @@ module ts { preserveConstEnums?: boolean; project?: string; removeComments?: boolean; + rootDir?: string; sourceMap?: boolean; sourceRoot?: string; suppressImplicitAnyIndexErrors?: boolean; diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 3291128a7a5..6823bfafac2 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -18,6 +18,7 @@ interface ProjectRunnerTestCase { runTest?: boolean; // Run the resulting test bug?: string; // If there is any bug associated with this test case noResolve?: boolean; + rootDir?: string; // --rootDir } interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase { @@ -160,7 +161,8 @@ class ProjectRunner extends RunnerBase { mapRoot: testCase.resolveMapRoot && testCase.mapRoot ? ts.sys.resolvePath(testCase.mapRoot) : testCase.mapRoot, sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot ? ts.sys.resolvePath(testCase.sourceRoot) : testCase.sourceRoot, module: moduleKind, - noResolve: testCase.noResolve + noResolve: testCase.noResolve, + rootDir: testCase.rootDir }; } @@ -344,6 +346,7 @@ class ProjectRunner extends RunnerBase { baselineCheck: testCase.baselineCheck, runTest: testCase.runTest, bug: testCase.bug, + rootDir: testCase.rootDir, resolvedInputFiles: ts.map(compilerResult.program.getSourceFiles(), inputFile => inputFile.fileName), emittedFiles: ts.map(compilerResult.outputFiles, outputFile => outputFile.emittedFileName) };