/// /// /// namespace ts { /* @internal */ export let programTime = 0; /* @internal */ export let emitTime = 0; /* @internal */ export let ioReadTime = 0; /* @internal */ export let ioWriteTime = 0; /** The version of the TypeScript compiler release */ export const version = "2.0.0"; const emptyArray: any[] = []; const defaultTypeRoots = ["node_modules/@types"]; export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string { while (true) { const fileName = combinePaths(searchPath, "tsconfig.json"); if (fileExists(fileName)) { return fileName; } const parentPath = getDirectoryPath(searchPath); if (parentPath === searchPath) { break; } searchPath = parentPath; } return undefined; } export function resolveTripleslashReference(moduleName: string, containingFile: string): string { const basePath = getDirectoryPath(containingFile); const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); return normalizePath(referencedFileName); } /* @internal */ export function computeCommonSourceDirectoryOfFilenames(fileNames: string[], currentDirectory: string, getCanonicalFileName: (fileName: string) => string): string { let commonPathComponents: string[]; const failed = forEach(fileNames, sourceFile => { // Each file contributes into common source file path const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory); sourcePathComponents.pop(); // The base file name is not part of the common directory path if (!commonPathComponents) { // first file commonPathComponents = sourcePathComponents; return; } for (let i = 0, n = Math.min(commonPathComponents.length, sourcePathComponents.length); i < n; i++) { if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) { if (i === 0) { // Failed to find any common path component return true; } // New common path found that is 0 -> i-1 commonPathComponents.length = i; break; } } // If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents if (sourcePathComponents.length < commonPathComponents.length) { commonPathComponents.length = sourcePathComponents.length; } }); // A common path can not be found when paths span multiple drives on windows, for example if (failed) { return ""; } if (!commonPathComponents) { // Can happen when all input files are .d.ts files return currentDirectory; } return getNormalizedPathFromPathComponents(commonPathComponents); } function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; function trace(host: ModuleResolutionHost, message: DiagnosticMessage): void { host.trace(formatMessage.apply(undefined, arguments)); } function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { return compilerOptions.traceResolution && host.trace !== undefined; } /* @internal */ export function hasZeroOrOneAsteriskCharacter(str: string): boolean { let seenAsterisk = false; for (let i = 0; i < str.length; i++) { if (str.charCodeAt(i) === CharacterCodes.asterisk) { if (!seenAsterisk) { seenAsterisk = true; } else { // have already seen asterisk return false; } } } return true; } function createResolvedModule(resolvedFileName: string, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations { return { resolvedModule: resolvedFileName ? { resolvedFileName, isExternalLibraryImport } : undefined, failedLookupLocations }; } function moduleHasNonRelativeName(moduleName: string): boolean { if (isRootedDiskPath(moduleName)) { return false; } const i = moduleName.lastIndexOf("./", 1); const startsWithDotSlashOrDotDotSlash = i === 0 || (i === 1 && moduleName.charCodeAt(0) === CharacterCodes.dot); return !startsWithDotSlashOrDotDotSlash; } interface ModuleResolutionState { host: ModuleResolutionHost; compilerOptions: CompilerOptions; traceEnabled: boolean; // skip .tsx files if jsx is not enabled skipTsx: boolean; } function tryReadTypesSection(packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string { let jsonContent: { typings?: string, types?: string, main?: string }; try { const jsonText = state.host.readFile(packageJsonPath); jsonContent = jsonText ? <{ typings?: string, types?: string, main?: string }>JSON.parse(jsonText) : {}; } catch (e) { // gracefully handle if readFile fails or returns not JSON jsonContent = {}; } let typesFile: string; let fieldName: string; // first try to read content of 'typings' section (backward compatibility) if (jsonContent.typings) { if (typeof jsonContent.typings === "string") { fieldName = "typings"; typesFile = jsonContent.typings; } else { if (state.traceEnabled) { trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "typings", typeof jsonContent.typings); } } } // then read 'types' if (!typesFile && jsonContent.types) { if (typeof jsonContent.types === "string") { fieldName = "types"; typesFile = jsonContent.types; } else { if (state.traceEnabled) { trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "types", typeof jsonContent.types); } } } if (typesFile) { const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile)); if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath); } return typesFilePath; } // Use the main module for inferring types if no types package specified and the allowJs is set if (state.compilerOptions.allowJs && jsonContent.main && typeof jsonContent.main === "string") { if (state.traceEnabled) { trace(state.host, Diagnostics.No_types_specified_in_package_json_but_allowJs_is_set_so_returning_main_value_of_0, jsonContent.main); } const mainFilePath = normalizePath(combinePaths(baseDirectory, jsonContent.main)); return mainFilePath; } return undefined; } const typeReferenceExtensions = [".d.ts"]; function getEffectiveTypeRoots(options: CompilerOptions, host: ModuleResolutionHost) { if (options.typeRoots) { return options.typeRoots; } let currentDirectory: string; if (options.configFilePath) { currentDirectory = getDirectoryPath(options.configFilePath); } else if (host.getCurrentDirectory) { currentDirectory = host.getCurrentDirectory(); } if (!currentDirectory) { return undefined; } return map(defaultTypeRoots, d => combinePaths(currentDirectory, d)); } /** * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups * is assumed to be the same as root directory of the project. */ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { const traceEnabled = isTraceEnabled(options, host); const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host: host, skipTsx: true, traceEnabled }; const typeRoots = getEffectiveTypeRoots(options, host); if (traceEnabled) { if (containingFile === undefined) { if (typeRoots === undefined) { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); } else { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); } } else { if (typeRoots === undefined) { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); } else { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); } } } const failedLookupLocations: string[] = []; // Check primary library paths if (typeRoots && typeRoots.length) { if (traceEnabled) { trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); } const primarySearchPaths = typeRoots; for (const typeRoot of primarySearchPaths) { const candidate = combinePaths(typeRoot, typeReferenceDirectiveName); const candidateDirectory = getDirectoryPath(candidate); const resolvedFile = loadNodeModuleFromDirectory(typeReferenceExtensions, candidate, failedLookupLocations, !directoryProbablyExists(candidateDirectory, host), moduleResolutionState); if (resolvedFile) { if (traceEnabled) { trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, true); } return { resolvedTypeReferenceDirective: { primary: true, resolvedFileName: resolvedFile }, failedLookupLocations }; } } } else { if (traceEnabled) { trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); } } let resolvedFile: string; let initialLocationForSecondaryLookup: string; if (containingFile) { initialLocationForSecondaryLookup = getDirectoryPath(containingFile); } if (initialLocationForSecondaryLookup !== undefined) { // check secondary locations if (traceEnabled) { trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); } resolvedFile = loadModuleFromNodeModules(typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState); if (traceEnabled) { if (resolvedFile) { trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, false); } else { trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); } } } else { if (traceEnabled) { trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder); } } return { resolvedTypeReferenceDirective: resolvedFile ? { primary: false, resolvedFileName: resolvedFile } : undefined, failedLookupLocations }; } export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); if (traceEnabled) { trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); } let moduleResolution = compilerOptions.moduleResolution; if (moduleResolution === undefined) { moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; if (traceEnabled) { trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); } } else { if (traceEnabled) { trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); } } let result: ResolvedModuleWithFailedLookupLocations; switch (moduleResolution) { case ModuleResolutionKind.NodeJs: result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host); break; case ModuleResolutionKind.Classic: result = classicNameResolver(moduleName, containingFile, compilerOptions, host); break; } if (traceEnabled) { if (result.resolvedModule) { trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); } else { trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); } } return result; } /* * Every module resolution kind can has its specific understanding how to load module from a specific path on disk * I.e. for path '/a/b/c': * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with * 'typings' entry or file 'index' with some supported extension * - Classic loader will only try to interpret '/a/b/c' as file. */ type ResolutionKindSpecificLoader = (candidate: string, extensions: string[], failedLookupLocations: string[], onlyRecordFailures: boolean, state: ModuleResolutionState) => string; /** * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to * mitigate differences between design time structure of the project and its runtime counterpart so the same import name * can be resolved successfully by TypeScript compiler and runtime module loader. * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will * fallback to standard resolution routine. * * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will * be '/a/b/c/d' * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names * will be resolved based on the content of the module name. * Structure of 'paths' compiler options * 'paths': { * pattern-1: [...substitutions], * pattern-2: [...substitutions], * ... * pattern-n: [...substitutions] * } * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. * If pattern contains '*' then to match pattern "*" module name must start with the and end with . * denotes part of the module name between and . * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module * from the candidate location. * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every * substitution in the list and replace '*' with string. If candidate location is not rooted it * will be converted to absolute using baseUrl. * For example: * baseUrl: /a/b/c * "paths": { * // match all module names * "*": [ * "*", // use matched name as is, * // will be looked as /a/b/c/ * * "folder1/*" // substitution will convert matched name to 'folder1/', * // since it is not rooted then final candidate location will be /a/b/c/folder1/ * ], * // match module names that start with 'components/' * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/ to '/root/components/folder1/', * // it is rooted so it will be final candidate location * } * * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if * they were in the same location. For example lets say there are two files * '/local/src/content/file1.ts' * '/shared/components/contracts/src/content/protocols/file2.ts' * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all * root dirs were merged together. * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: * '/local/src/content/protocols/file2' and try to load it - failure. * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. */ function tryLoadModuleUsingOptionalResolutionSettings(moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, failedLookupLocations: string[], supportedExtensions: string[], state: ModuleResolutionState): string { if (moduleHasNonRelativeName(moduleName)) { return tryLoadModuleUsingBaseUrl(moduleName, loader, failedLookupLocations, supportedExtensions, state); } else { return tryLoadModuleUsingRootDirs(moduleName, containingDirectory, loader, failedLookupLocations, supportedExtensions, state); } } function tryLoadModuleUsingRootDirs(moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, failedLookupLocations: string[], supportedExtensions: string[], state: ModuleResolutionState): string { if (!state.compilerOptions.rootDirs) { return undefined; } if (state.traceEnabled) { trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); } const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); let matchedRootDir: string; let matchedNormalizedPrefix: string; for (const rootDir of state.compilerOptions.rootDirs) { // rootDirs are expected to be absolute // in case of tsconfig.json this will happen automatically - compiler will expand relative names // using location of tsconfig.json as base location let normalizedRoot = normalizePath(rootDir); if (!endsWith(normalizedRoot, directorySeparator)) { normalizedRoot += directorySeparator; } const isLongestMatchingPrefix = startsWith(candidate, normalizedRoot) && (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); if (state.traceEnabled) { trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); } if (isLongestMatchingPrefix) { matchedNormalizedPrefix = normalizedRoot; matchedRootDir = rootDir; } } if (matchedNormalizedPrefix) { if (state.traceEnabled) { trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); } const suffix = candidate.substr(matchedNormalizedPrefix.length); // first - try to load from a initial location if (state.traceEnabled) { trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); } const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(containingDirectory, state.host), state); if (resolvedFileName) { return resolvedFileName; } if (state.traceEnabled) { trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); } // then try to resolve using remaining entries in rootDirs for (const rootDir of state.compilerOptions.rootDirs) { if (rootDir === matchedRootDir) { // skip the initially matched entry continue; } const candidate = combinePaths(normalizePath(rootDir), suffix); if (state.traceEnabled) { trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); } const baseDirectory = getDirectoryPath(candidate); const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(baseDirectory, state.host), state); if (resolvedFileName) { return resolvedFileName; } } if (state.traceEnabled) { trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); } } return undefined; } function tryLoadModuleUsingBaseUrl(moduleName: string, loader: ResolutionKindSpecificLoader, failedLookupLocations: string[], supportedExtensions: string[], state: ModuleResolutionState): string { if (!state.compilerOptions.baseUrl) { return undefined; } if (state.traceEnabled) { trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, state.compilerOptions.baseUrl, moduleName); } // string is for exact match let matchedPattern: Pattern | string | undefined = undefined; if (state.compilerOptions.paths) { if (state.traceEnabled) { trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); } matchedPattern = matchPatternOrExact(getKeys(state.compilerOptions.paths), moduleName); } if (matchedPattern) { const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName); const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern); if (state.traceEnabled) { trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); } for (const subst of state.compilerOptions.paths[matchedPatternText]) { const path = matchedStar ? subst.replace("*", matchedStar) : subst; const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, path)); if (state.traceEnabled) { trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); } const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); if (resolvedFileName) { return resolvedFileName; } } return undefined; } else { const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, moduleName)); if (state.traceEnabled) { trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, state.compilerOptions.baseUrl, candidate); } return loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); } } /** * patternStrings contains both pattern strings (containing "*") and regular strings. * Return an exact match if possible, or a pattern match, or undefined. * (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.) */ function matchPatternOrExact(patternStrings: string[], candidate: string): string | Pattern | undefined { const patterns: Pattern[] = []; for (const patternString of patternStrings) { const pattern = tryParsePattern(patternString); if (pattern) { patterns.push(pattern); } else if (patternString === candidate) { // pattern was matched as is - no need to search further return patternString; } } return findBestPatternMatch(patterns, _ => _, candidate); } function patternText({prefix, suffix}: Pattern): string { return `${prefix}*${suffix}`; } /** * Given that candidate matches pattern, returns the text matching the '*'. * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar" */ function matchedText(pattern: Pattern, candidate: string): string { Debug.assert(isPatternMatch(pattern, candidate)); return candidate.substr(pattern.prefix.length, candidate.length - pattern.suffix.length); } /** Return the object corresponding to the best pattern to match `candidate`. */ /* @internal */ export function findBestPatternMatch(values: T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined { let matchedValue: T | undefined = undefined; // use length of prefix as betterness criteria let longestMatchPrefixLength = -1; for (const v of values) { const pattern = getPattern(v); if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { longestMatchPrefixLength = pattern.prefix.length; matchedValue = v; } } return matchedValue; } function isPatternMatch({prefix, suffix}: Pattern, candidate: string) { return candidate.length >= prefix.length + suffix.length && startsWith(candidate, prefix) && endsWith(candidate, suffix); } /* @internal */ export function tryParsePattern(pattern: string): Pattern | undefined { // This should be verified outside of here and a proper error thrown. Debug.assert(hasZeroOrOneAsteriskCharacter(pattern)); const indexOfStar = pattern.indexOf("*"); return indexOfStar === -1 ? undefined : { prefix: pattern.substr(0, indexOfStar), suffix: pattern.substr(indexOfStar + 1) }; } export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const containingDirectory = getDirectoryPath(containingFile); const supportedExtensions = getSupportedExtensions(compilerOptions); const traceEnabled = isTraceEnabled(compilerOptions, host); const failedLookupLocations: string[] = []; const state = { compilerOptions, host, traceEnabled, skipTsx: false }; let resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, nodeLoadModuleByRelativeName, failedLookupLocations, supportedExtensions, state); let isExternalLibraryImport = false; if (!resolvedFileName) { if (moduleHasNonRelativeName(moduleName)) { if (traceEnabled) { trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName); } resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state); isExternalLibraryImport = resolvedFileName !== undefined; } else { const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); resolvedFileName = nodeLoadModuleByRelativeName(candidate, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state); } } if (resolvedFileName && host.realpath) { const originalFileName = resolvedFileName; resolvedFileName = normalizePath(host.realpath(resolvedFileName)); if (traceEnabled) { trace(host, Diagnostics.Resolving_real_path_for_0_result_1, originalFileName, resolvedFileName); } } return createResolvedModule(resolvedFileName, isExternalLibraryImport, failedLookupLocations); } function nodeLoadModuleByRelativeName(candidate: string, supportedExtensions: string[], failedLookupLocations: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { if (state.traceEnabled) { trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0, candidate); } const resolvedFileName = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, onlyRecordFailures, state); return resolvedFileName || loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, onlyRecordFailures, state); } /* @internal */ export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean { // if host does not support 'directoryExists' assume that directory will exist return !host.directoryExists || host.directoryExists(directoryName); } /** * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. */ function loadModuleFromFile(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { // First try to keep/add an extension: importing "./foo.ts" can be matched by a file "./foo.ts", and "./foo" by "./foo.d.ts" const resolvedByAddingOrKeepingExtension = loadModuleFromFileWorker(candidate, extensions, failedLookupLocation, onlyRecordFailures, state); if (resolvedByAddingOrKeepingExtension) { return resolvedByAddingOrKeepingExtension; } // Then try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one, e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" if (hasJavaScriptFileExtension(candidate)) { const extensionless = removeFileExtension(candidate); if (state.traceEnabled) { const extension = candidate.substring(extensionless.length); trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); } return loadModuleFromFileWorker(extensionless, extensions, failedLookupLocation, onlyRecordFailures, state); } } function loadModuleFromFileWorker(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { if (!onlyRecordFailures) { // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing const directory = getDirectoryPath(candidate); if (directory) { onlyRecordFailures = !directoryProbablyExists(directory, state.host); } } return forEach(extensions, tryLoad); function tryLoad(ext: string): string { if (state.skipTsx && isJsxOrTsxExtension(ext)) { return undefined; } const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext; if (!onlyRecordFailures && state.host.fileExists(fileName)) { if (state.traceEnabled) { trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); } return fileName; } else { if (state.traceEnabled) { trace(state.host, Diagnostics.File_0_does_not_exist, fileName); } failedLookupLocation.push(fileName); return undefined; } } } function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { const packageJsonPath = combinePaths(candidate, "package.json"); const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, state.host); if (directoryExists && state.host.fileExists(packageJsonPath)) { if (state.traceEnabled) { trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath); } const typesFile = tryReadTypesSection(packageJsonPath, candidate, state); if (typesFile) { const result = loadModuleFromFile(typesFile, extensions, failedLookupLocation, !directoryProbablyExists(getDirectoryPath(typesFile), state.host), state); if (result) { return result; } } else { if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_does_not_have_types_field); } } } else { if (state.traceEnabled) { trace(state.host, Diagnostics.File_0_does_not_exist, packageJsonPath); } // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results failedLookupLocation.push(packageJsonPath); } return loadModuleFromFile(combinePaths(candidate, "index"), extensions, failedLookupLocation, !directoryExists, state); } function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { const nodeModulesFolder = combinePaths(directory, "node_modules"); const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName)); const supportedExtensions = getSupportedExtensions(state.compilerOptions); let result = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, !nodeModulesFolderExists, state); if (result) { return result; } result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state); if (result) { return result; } } function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { directory = normalizeSlashes(directory); while (true) { const baseName = getBaseFileName(directory); if (baseName !== "node_modules") { const result = // first: try to load module as-is loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state) || // second: try to load module from the scope '@types' loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state); if (result) { return result; } } const parentPath = getDirectoryPath(directory); if (parentPath === directory) { break; } directory = parentPath; } return undefined; } export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); const state = { compilerOptions, host, traceEnabled, skipTsx: !compilerOptions.jsx }; const failedLookupLocations: string[] = []; const supportedExtensions = getSupportedExtensions(compilerOptions); let containingDirectory = getDirectoryPath(containingFile); const resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, loadModuleFromFile, failedLookupLocations, supportedExtensions, state); if (resolvedFileName) { return createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/false, failedLookupLocations); } let referencedSourceFile: string; if (moduleHasNonRelativeName(moduleName)) { while (true) { const searchName = normalizePath(combinePaths(containingDirectory, moduleName)); referencedSourceFile = loadModuleFromFile(searchName, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state); if (referencedSourceFile) { break; } const parentPath = getDirectoryPath(containingDirectory); if (parentPath === containingDirectory) { break; } containingDirectory = parentPath; } } else { const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); referencedSourceFile = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state); } return referencedSourceFile ? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations } : { resolvedModule: undefined, failedLookupLocations }; } /* @internal */ export const defaultInitCompilerOptions: CompilerOptions = { module: ModuleKind.CommonJS, target: ScriptTarget.ES5, noImplicitAny: false, sourceMap: false, }; interface OutputFingerprint { hash: string; byteOrderMark: boolean; mtime: Date; } export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { const existingDirectories: Map = {}; function getCanonicalFileName(fileName: string): string { // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. // otherwise use toLowerCase as a canonical form. return sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); } // returned by CScript sys environment const unsupportedFileEncodingErrorCode = -2147024809; function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { const start = new Date().getTime(); text = sys.readFile(fileName, options.charset); ioReadTime += new Date().getTime() - start; } catch (e) { if (onError) { onError(e.number === unsupportedFileEncodingErrorCode ? createCompilerDiagnostic(Diagnostics.Unsupported_file_encoding).messageText : e.message); } text = ""; } return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined; } function directoryExists(directoryPath: string): boolean { if (hasProperty(existingDirectories, directoryPath)) { return true; } if (sys.directoryExists(directoryPath)) { existingDirectories[directoryPath] = true; return true; } return false; } function ensureDirectoriesExist(directoryPath: string) { if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { const parentDirectory = getDirectoryPath(directoryPath); ensureDirectoriesExist(parentDirectory); sys.createDirectory(directoryPath); } } let outputFingerprints: Map; function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { if (!outputFingerprints) { outputFingerprints = {}; } const hash = sys.createHash(data); const mtimeBefore = sys.getModifiedTime(fileName); if (mtimeBefore && hasProperty(outputFingerprints, fileName)) { const fingerprint = outputFingerprints[fileName]; // If output has not been changed, and the file has no external modification if (fingerprint.byteOrderMark === writeByteOrderMark && fingerprint.hash === hash && fingerprint.mtime.getTime() === mtimeBefore.getTime()) { return; } } sys.writeFile(fileName, data, writeByteOrderMark); const mtimeAfter = sys.getModifiedTime(fileName); outputFingerprints[fileName] = { hash, byteOrderMark: writeByteOrderMark, mtime: mtimeAfter }; } function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { const start = new Date().getTime(); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { writeFileIfUpdated(fileName, data, writeByteOrderMark); } else { sys.writeFile(fileName, data, writeByteOrderMark); } ioWriteTime += new Date().getTime() - start; } catch (e) { if (onError) { onError(e.message); } } } function getDefaultLibLocation(): string { return getDirectoryPath(normalizePath(sys.getExecutingFilePath())); } const newLine = getNewLineCharacter(options); const realpath = sys.realpath && ((path: string) => sys.realpath(path)); return { getSourceFile, getDefaultLibLocation, getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile, getCurrentDirectory: memoize(() => sys.getCurrentDirectory()), useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists: fileName => sys.fileExists(fileName), readFile: fileName => sys.readFile(fileName), trace: (s: string) => sys.write(s + newLine), directoryExists: directoryName => sys.directoryExists(directoryName), getDirectories: (path: string) => sys.getDirectories(path), realpath }; } export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[] { let diagnostics = program.getOptionsDiagnostics(cancellationToken).concat( program.getSyntacticDiagnostics(sourceFile, cancellationToken), program.getGlobalDiagnostics(cancellationToken), program.getSemanticDiagnostics(sourceFile, cancellationToken)); if (program.getCompilerOptions().declaration) { diagnostics = diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken)); } return sortAndDeduplicateDiagnostics(diagnostics); } export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string { if (typeof messageText === "string") { return messageText; } else { let diagnosticChain = messageText; let result = ""; let indent = 0; while (diagnosticChain) { if (indent) { result += newLine; for (let i = 0; i < indent; i++) { result += " "; } } result += diagnosticChain.messageText; indent++; diagnosticChain = diagnosticChain.next; } return result; } } function loadWithLocalCache(names: string[], containingFile: string, loader: (name: string, containingFile: string) => T): T[] { if (names.length === 0) { return []; } const resolutions: T[] = []; const cache: Map = {}; for (const name of names) { let result: T; if (hasProperty(cache, name)) { result = cache[name]; } else { result = loader(name, containingFile); cache[name] = result; } resolutions.push(result); } return resolutions; } function getInferredTypesRoot(options: CompilerOptions, rootFiles: string[], host: CompilerHost) { return computeCommonSourceDirectoryOfFilenames(rootFiles, host.getCurrentDirectory(), f => host.getCanonicalFileName(f)); } /** * Given a set of options and a set of root files, returns the set of type directive names * that should be included for this program automatically. * This list could either come from the config file, * or from enumerating the types root + initial secondary types lookup location. * More type directives might appear in the program later as a result of loading actual source files; * this list is only the set of defaults that are implicitly included. */ export function getAutomaticTypeDirectiveNames(options: CompilerOptions, rootFiles: string[], host: CompilerHost): string[] { // Use explicit type list from tsconfig.json if (options.types) { return options.types; } // Walk the primary type lookup locations let result: string[] = []; if (host.directoryExists && host.getDirectories) { const typeRoots = getEffectiveTypeRoots(options, host); if (typeRoots) { for (const root of typeRoots) { if (host.directoryExists(root)) { result = result.concat(host.getDirectories(root)); } } } } return result; } export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program { let program: Program; let files: SourceFile[] = []; let commonSourceDirectory: string; let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; let classifiableNames: Map; let resolvedTypeReferenceDirectives: Map = {}; let fileProcessingDiagnostics = createDiagnosticCollection(); // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. // This works as imported modules are discovered recursively in a depth first manner, specifically: // - For each root file, findSourceFile is called. // - This calls processImportedModules for each module imported in the source file. // - This calls resolveModuleNames, and then calls findSourceFile for each resolved module. // As all these operations happen - and are nested - within the createProgram call, they close over the below variables. // The current resolution depth is tracked by incrementing/decrementing as the depth first search progresses. const maxNodeModulesJsDepth = typeof options.maxNodeModuleJsDepth === "number" ? options.maxNodeModuleJsDepth : 2; let currentNodeModulesJsDepth = 0; // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. const modulesWithElidedImports: Map = {}; // Track source files that are JavaScript files found by searching under node_modules, as these shouldn't be compiled. const jsFilesFoundSearchingNodeModules: Map = {}; const start = new Date().getTime(); host = host || createCompilerHost(options); let skipDefaultLib = options.noLib; const programDiagnostics = createDiagnosticCollection(); const currentDirectory = host.getCurrentDirectory(); const supportedExtensions = getSupportedExtensions(options); // Map storing if there is emit blocking diagnostics for given input const hasEmitBlockingDiagnostics = createFileMap(getCanonicalFileName); let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModule[]; if (host.resolveModuleNames) { resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); } else { const loader = (moduleName: string, containingFile: string) => resolveModuleName(moduleName, containingFile, options, host).resolvedModule; resolveModuleNamesWorker = (moduleNames, containingFile) => loadWithLocalCache(moduleNames, containingFile, loader); } let resolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[], containingFile: string) => ResolvedTypeReferenceDirective[]; if (host.resolveTypeReferenceDirectives) { resolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile) => host.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); } else { const loader = (typesRef: string, containingFile: string) => resolveTypeReferenceDirective(typesRef, containingFile, options, host).resolvedTypeReferenceDirective; resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile) => loadWithLocalCache(typeReferenceDirectiveNames, containingFile, loader); } const filesByName = createFileMap(); // stores 'filename -> file association' ignoring case // used to track cases when two file names differ only in casing const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createFileMap(fileName => fileName.toLowerCase()) : undefined; if (!tryReuseStructureFromOldProgram()) { forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false)); // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders const typeReferences: string[] = getAutomaticTypeDirectiveNames(options, rootNames, host); if (typeReferences) { const inferredRoot = getInferredTypesRoot(options, rootNames, host); const containingFilename = combinePaths(inferredRoot, "__inferred type names__.ts"); const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename); for (let i = 0; i < typeReferences.length; i++) { processTypeReferenceDirective(typeReferences[i], resolutions[i]); } } // Do not process the default library if: // - The '--noLib' flag is used. // - A 'no-default-lib' reference comment is encountered in // processing the root files. if (!skipDefaultLib) { // 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) { processRootFile(host.getDefaultLibFileName(options), /*isDefaultLib*/ true); } else { const libDirectory = host.getDefaultLibLocation ? host.getDefaultLibLocation() : getDirectoryPath(host.getDefaultLibFileName(options)); forEach(options.lib, libFileName => { processRootFile(combinePaths(libDirectory, libFileName), /*isDefaultLib*/ true); }); } } } // unconditionally set oldProgram to undefined to prevent it from being captured in closure oldProgram = undefined; program = { getRootFileNames: () => rootNames, getSourceFile, getSourceFileByPath, getSourceFiles: () => files, getCompilerOptions: () => options, getSyntacticDiagnostics, getOptionsDiagnostics, getGlobalDiagnostics, getSemanticDiagnostics, getDeclarationDiagnostics, getTypeChecker, getClassifiableNames, getDiagnosticsProducingTypeChecker, getCommonSourceDirectory, emit, getCurrentDirectory: () => currentDirectory, getNodeCount: () => getDiagnosticsProducingTypeChecker().getNodeCount(), getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(), getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(), getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(), getFileProcessingDiagnostics: () => fileProcessingDiagnostics, getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives }; verifyCompilerOptions(); programTime += new Date().getTime() - start; return program; function getCommonSourceDirectory() { if (typeof commonSourceDirectory === "undefined") { if (options.rootDir && checkSourceFilesBelongToPath(files, options.rootDir)) { // If a rootDir is specified and is valid use it as the commonSourceDirectory commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); } else { 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; } } return commonSourceDirectory; } function getClassifiableNames() { if (!classifiableNames) { // Initialize a checker so that all our files are bound. getTypeChecker(); classifiableNames = {}; for (const sourceFile of files) { copyMap(sourceFile.classifiableNames, classifiableNames); } } return classifiableNames; } function tryReuseStructureFromOldProgram(): boolean { if (!oldProgram) { return false; } // check properties that can affect structure of the program or module resolution strategy // if any of these properties has changed - structure cannot be reused const oldOptions = oldProgram.getCompilerOptions(); if ((oldOptions.module !== options.module) || (oldOptions.moduleResolution !== options.moduleResolution) || (oldOptions.noResolve !== options.noResolve) || (oldOptions.target !== options.target) || (oldOptions.noLib !== options.noLib) || (oldOptions.jsx !== options.jsx) || (oldOptions.allowJs !== options.allowJs) || (oldOptions.rootDir !== options.rootDir) || (oldOptions.configFilePath !== options.configFilePath) || (oldOptions.baseUrl !== options.baseUrl) || (oldOptions.maxNodeModuleJsDepth !== options.maxNodeModuleJsDepth) || !arrayIsEqualTo(oldOptions.typeRoots, oldOptions.typeRoots) || !arrayIsEqualTo(oldOptions.rootDirs, options.rootDirs) || !mapIsEqualTo(oldOptions.paths, options.paths)) { return false; } Debug.assert(!oldProgram.structureIsReused); // there is an old program, check if we can reuse its structure const oldRootNames = oldProgram.getRootFileNames(); if (!arrayIsEqualTo(oldRootNames, rootNames)) { return false; } if (!arrayIsEqualTo(options.types, oldOptions.types)) { return false; } // check if program source files has changed in the way that can affect structure of the program const newSourceFiles: SourceFile[] = []; const filePaths: Path[] = []; const modifiedSourceFiles: SourceFile[] = []; for (const oldSourceFile of oldProgram.getSourceFiles()) { let newSourceFile = host.getSourceFileByPath ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target) : host.getSourceFile(oldSourceFile.fileName, options.target); if (!newSourceFile) { return false; } newSourceFile.path = oldSourceFile.path; filePaths.push(newSourceFile.path); if (oldSourceFile !== newSourceFile) { if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { // value of no-default-lib has changed // this will affect if default library is injected into the list of files return false; } // check tripleslash references if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { // tripleslash references has changed return false; } // check imports and module augmentations collectExternalModuleReferences(newSourceFile); if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) { // imports has changed return false; } if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) { // moduleAugmentations has changed return false; } if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) { // 'types' references has changed return false; } const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory); if (resolveModuleNamesWorker) { const moduleNames = map(concatenate(newSourceFile.imports, newSourceFile.moduleAugmentations), getTextOfLiteral); const resolutions = resolveModuleNamesWorker(moduleNames, newSourceFilePath); // ensure that module resolution results are still correct const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); if (resolutionsChanged) { return false; } } if (resolveTypeReferenceDirectiveNamesWorker) { const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, x => x.fileName); const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath); // ensure that types resolutions are still correct const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); if (resolutionsChanged) { return false; } } // pass the cache of module/types resolutions from the old source file newSourceFile.resolvedModules = oldSourceFile.resolvedModules; newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; modifiedSourceFiles.push(newSourceFile); } else { // file has no changes - use it as is newSourceFile = oldSourceFile; } // if file has passed all checks it should be safe to reuse it newSourceFiles.push(newSourceFile); } // update fileName -> file mapping for (let i = 0, len = newSourceFiles.length; i < len; i++) { filesByName.set(filePaths[i], newSourceFiles[i]); } files = newSourceFiles; fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); for (const modifiedFile of modifiedSourceFiles) { fileProcessingDiagnostics.reattachFileDiagnostics(modifiedFile); } resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives(); oldProgram.structureIsReused = true; return true; } function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { return { getCanonicalFileName, getCommonSourceDirectory: program.getCommonSourceDirectory, getCompilerOptions: program.getCompilerOptions, getCurrentDirectory: () => currentDirectory, getNewLine: () => host.getNewLine(), getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, getSourceFiles: program.getSourceFiles, getFilesFromNodeModules: () => jsFilesFoundSearchingNodeModules, writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, }; } function getDiagnosticsProducingTypeChecker() { return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true)); } function getTypeChecker() { return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); } function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { return runWithCancellationToken(() => emitWorker(this, sourceFile, writeFileCallback, cancellationToken)); } function isEmitBlocked(emitFileName: string): boolean { return hasEmitBlockingDiagnostics.contains(toPath(emitFileName, currentDirectory, getCanonicalFileName)); } function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken): EmitResult { let declarationDiagnostics: 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).concat( 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 }; } } // Create the emit resolver outside of the "emitTime" tracking code below. That way // any cost associated with it (like type checking) are appropriate associated with // the type-checking counter. // // If the -out option is specified, we should not pass the source file to getEmitResolver. // 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 start = new Date().getTime(); const emitResult = emitFiles( emitResolver, getEmitHost(writeFileCallback), sourceFile); emitTime += new Date().getTime() - start; return emitResult; } function getSourceFile(fileName: string): SourceFile { return getSourceFileByPath(toPath(fileName, currentDirectory, getCanonicalFileName)); } function getSourceFileByPath(path: Path): SourceFile { return filesByName.get(path); } function getDiagnosticsHelper( sourceFile: SourceFile, getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[], cancellationToken: CancellationToken): Diagnostic[] { if (sourceFile) { return getDiagnostics(sourceFile, cancellationToken); } const allDiagnostics: Diagnostic[] = []; forEach(program.getSourceFiles(), sourceFile => { if (cancellationToken) { cancellationToken.throwIfCancellationRequested(); } addRange(allDiagnostics, getDiagnostics(sourceFile, cancellationToken)); }); return sortAndDeduplicateDiagnostics(allDiagnostics); } function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); } function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); } function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { const options = program.getCompilerOptions(); // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) if (!sourceFile || options.out || options.outFile) { return getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); } else { return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); } } function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return sourceFile.parseDiagnostics; } function runWithCancellationToken(func: () => T): T { try { return func(); } catch (e) { if (e instanceof OperationCanceledException) { // We were canceled while performing the operation. Because our type checker // might be a bad state, we need to throw it away. // // Note: we are overly aggressive here. We do not actually *have* to throw away // the "noDiagnosticsTypeChecker". However, for simplicity, i'd like to keep // the lifetimes of these two TypeCheckers the same. Also, we generally only // cancel when the user has made a change anyways. And, in that case, we (the // program instance) will get thrown away anyways. So trying to keep one of // these type checkers alive doesn't serve much purpose. noDiagnosticsTypeChecker = undefined; diagnosticsProducingTypeChecker = undefined; } throw e; } } function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return runWithCancellationToken(() => { const typeChecker = getDiagnosticsProducingTypeChecker(); Debug.assert(!!sourceFile.bindDiagnostics); const bindDiagnostics = sourceFile.bindDiagnostics; // For JavaScript files, we don't want to report the normal typescript semantic errors. // Instead, we just report errors for using TypeScript-only constructs from within a // JavaScript file. const checkDiagnostics = isSourceFileJavaScript(sourceFile) ? getJavaScriptSemanticDiagnosticsForFile(sourceFile, cancellationToken) : typeChecker.getDiagnostics(sourceFile, cancellationToken); const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName); const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); return bindDiagnostics.concat(checkDiagnostics).concat(fileProcessingDiagnosticsInFile).concat(programDiagnosticsInFile); }); } function getJavaScriptSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return runWithCancellationToken(() => { const diagnostics: Diagnostic[] = []; walk(sourceFile); return diagnostics; function walk(node: Node): boolean { if (!node) { return false; } switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_a_ts_file)); return true; case SyntaxKind.ExportAssignment: if ((node).isExportEquals) { diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_a_ts_file)); return true; } break; case SyntaxKind.ClassDeclaration: let classDeclaration = node; if (checkModifiers(classDeclaration.modifiers) || checkTypeParameters(classDeclaration.typeParameters)) { return true; } break; case SyntaxKind.HeritageClause: let heritageClause = node; if (heritageClause.token === SyntaxKind.ImplementsKeyword) { diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_a_ts_file)); return true; } break; case SyntaxKind.InterfaceDeclaration: diagnostics.push(createDiagnosticForNode(node, Diagnostics.interface_declarations_can_only_be_used_in_a_ts_file)); return true; case SyntaxKind.ModuleDeclaration: diagnostics.push(createDiagnosticForNode(node, Diagnostics.module_declarations_can_only_be_used_in_a_ts_file)); return true; case SyntaxKind.TypeAliasDeclaration: diagnostics.push(createDiagnosticForNode(node, Diagnostics.type_aliases_can_only_be_used_in_a_ts_file)); return true; case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionDeclaration: const functionDeclaration = node; if (checkModifiers(functionDeclaration.modifiers) || checkTypeParameters(functionDeclaration.typeParameters) || checkTypeAnnotation(functionDeclaration.type)) { return true; } break; case SyntaxKind.VariableStatement: const variableStatement = node; if (checkModifiers(variableStatement.modifiers)) { return true; } break; case SyntaxKind.VariableDeclaration: const variableDeclaration = node; if (checkTypeAnnotation(variableDeclaration.type)) { return true; } break; case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: const expression = node; if (expression.typeArguments && expression.typeArguments.length > 0) { const start = expression.typeArguments.pos; diagnostics.push(createFileDiagnostic(sourceFile, start, expression.typeArguments.end - start, Diagnostics.type_arguments_can_only_be_used_in_a_ts_file)); return true; } break; case SyntaxKind.Parameter: const parameter = node; if (parameter.modifiers) { const start = parameter.modifiers.pos; diagnostics.push(createFileDiagnostic(sourceFile, start, parameter.modifiers.end - start, Diagnostics.parameter_modifiers_can_only_be_used_in_a_ts_file)); return true; } if (parameter.questionToken) { diagnostics.push(createDiagnosticForNode(parameter.questionToken, Diagnostics._0_can_only_be_used_in_a_ts_file, "?")); return true; } if (parameter.type) { diagnostics.push(createDiagnosticForNode(parameter.type, Diagnostics.types_can_only_be_used_in_a_ts_file)); return true; } break; case SyntaxKind.PropertyDeclaration: const propertyDeclaration = node; if (propertyDeclaration.modifiers) { for (const modifier of propertyDeclaration.modifiers) { if (modifier.kind !== SyntaxKind.StaticKeyword) { diagnostics.push(createDiagnosticForNode(modifier, Diagnostics._0_can_only_be_used_in_a_ts_file, tokenToString(modifier.kind))); return true; } } } if (checkTypeAnnotation((node).type)) { return true; } break; case SyntaxKind.EnumDeclaration: diagnostics.push(createDiagnosticForNode(node, Diagnostics.enum_declarations_can_only_be_used_in_a_ts_file)); return true; case SyntaxKind.TypeAssertionExpression: let typeAssertionExpression = node; diagnostics.push(createDiagnosticForNode(typeAssertionExpression.type, Diagnostics.type_assertion_expressions_can_only_be_used_in_a_ts_file)); return true; case SyntaxKind.Decorator: if (!options.experimentalDecorators) { diagnostics.push(createDiagnosticForNode(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_to_remove_this_warning)); } return true; } return forEachChild(node, walk); } function checkTypeParameters(typeParameters: NodeArray): boolean { if (typeParameters) { const start = typeParameters.pos; diagnostics.push(createFileDiagnostic(sourceFile, start, typeParameters.end - start, Diagnostics.type_parameter_declarations_can_only_be_used_in_a_ts_file)); return true; } return false; } function checkTypeAnnotation(type: TypeNode): boolean { if (type) { diagnostics.push(createDiagnosticForNode(type, Diagnostics.types_can_only_be_used_in_a_ts_file)); return true; } return false; } function checkModifiers(modifiers: ModifiersArray): boolean { if (modifiers) { for (const modifier of modifiers) { switch (modifier.kind) { case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.ReadonlyKeyword: case SyntaxKind.DeclareKeyword: diagnostics.push(createDiagnosticForNode(modifier, Diagnostics._0_can_only_be_used_in_a_ts_file, tokenToString(modifier.kind))); return true; // These are all legal modifiers. case SyntaxKind.StaticKeyword: case SyntaxKind.ExportKeyword: case SyntaxKind.ConstKeyword: case SyntaxKind.DefaultKeyword: case SyntaxKind.AbstractKeyword: } } } return false; } }); } function getDeclarationDiagnosticsWorker(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return runWithCancellationToken(() => { const resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken); // Don't actually write any files since we're just getting diagnostics. const writeFile: WriteFileCallback = () => { }; return ts.getDeclarationDiagnostics(getEmitHost(writeFile), resolver, sourceFile); }); } function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return isDeclarationFile(sourceFile) ? [] : getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); } function getOptionsDiagnostics(): Diagnostic[] { const allDiagnostics: Diagnostic[] = []; addRange(allDiagnostics, fileProcessingDiagnostics.getGlobalDiagnostics()); addRange(allDiagnostics, programDiagnostics.getGlobalDiagnostics()); return sortAndDeduplicateDiagnostics(allDiagnostics); } function getGlobalDiagnostics(): Diagnostic[] { const allDiagnostics: Diagnostic[] = []; addRange(allDiagnostics, getDiagnosticsProducingTypeChecker().getGlobalDiagnostics()); return sortAndDeduplicateDiagnostics(allDiagnostics); } function hasExtension(fileName: string): boolean { return getBaseFileName(fileName).indexOf(".") >= 0; } function processRootFile(fileName: string, isDefaultLib: boolean) { processSourceFile(normalizePath(fileName), isDefaultLib, /*isReference*/ true); } function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean { return a.fileName === b.fileName; } function moduleNameIsEqualTo(a: LiteralExpression, b: LiteralExpression): boolean { return a.text === b.text; } function getTextOfLiteral(literal: LiteralExpression): string { return literal.text; } function collectExternalModuleReferences(file: SourceFile): void { if (file.imports) { return; } const isJavaScriptFile = isSourceFileJavaScript(file); const isExternalModuleFile = isExternalModule(file); let imports: LiteralExpression[]; let moduleAugmentations: LiteralExpression[]; for (const node of file.statements) { collectModuleReferences(node, /*inAmbientModule*/ false); if (isJavaScriptFile) { collectRequireCalls(node); } } file.imports = imports || emptyArray; file.moduleAugmentations = moduleAugmentations || emptyArray; return; function collectModuleReferences(node: Node, inAmbientModule: boolean): void { switch (node.kind) { case SyntaxKind.ImportDeclaration: case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.ExportDeclaration: let moduleNameExpr = getExternalModuleName(node); if (!moduleNameExpr || moduleNameExpr.kind !== SyntaxKind.StringLiteral) { break; } if (!(moduleNameExpr).text) { break; } // TypeScript 1.0 spec (April 2014): 12.1.6 // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules // only through top - level external module names. Relative external module names are not permitted. if (!inAmbientModule || !isExternalModuleNameRelative((moduleNameExpr).text)) { (imports || (imports = [])).push(moduleNameExpr); } break; case SyntaxKind.ModuleDeclaration: if (isAmbientModule(node) && (inAmbientModule || node.flags & NodeFlags.Ambient || isDeclarationFile(file))) { const moduleName = (node).name; // Ambient module declarations can be interpreted as augmentations for some existing external modules. // This will happen in two cases: // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name // immediately nested in top level ambient module declaration . if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(moduleName.text))) { (moduleAugmentations || (moduleAugmentations = [])).push(moduleName); } else if (!inAmbientModule) { // An AmbientExternalModuleDeclaration declares an external module. // This type of declaration is permitted only in the global module. // The StringLiteral must specify a top - level external module name. // Relative external module names are not permitted // NOTE: body of ambient module is always a module block, if it exists const body = (node).body; if (body) { for (const statement of body.statements) { collectModuleReferences(statement, /*inAmbientModule*/ true); } } } } } } function collectRequireCalls(node: Node): void { if (isRequireCall(node, /*checkArgumentIsStringLiteral*/true)) { (imports || (imports = [])).push((node).arguments[0]); } else { forEachChild(node, collectRequireCalls); } } } /** * 'isReference' indicates whether the file was brought in via a reference directive (rather than an import declaration) */ function processSourceFile(fileName: string, isDefaultLib: boolean, isReference: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) { let diagnosticArgument: string[]; let diagnostic: DiagnosticMessage; if (hasExtension(fileName)) { if (!options.allowNonTsExtensions && !forEach(supportedExtensions, extension => fileExtensionIs(host.getCanonicalFileName(fileName), extension))) { diagnostic = Diagnostics.File_0_has_unsupported_extension_The_only_supported_extensions_are_1; diagnosticArgument = [fileName, "'" + supportedExtensions.join("', '") + "'"]; } else if (!findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd)) { diagnostic = Diagnostics.File_0_not_found; diagnosticArgument = [fileName]; } else if (refFile && host.getCanonicalFileName(fileName) === host.getCanonicalFileName(refFile.fileName)) { diagnostic = Diagnostics.A_file_cannot_have_a_reference_to_itself; diagnosticArgument = [fileName]; } } else { const nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd); if (!nonTsFile) { if (options.allowNonTsExtensions) { diagnostic = Diagnostics.File_0_not_found; diagnosticArgument = [fileName]; } else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, toPath(fileName + extension, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd))) { diagnostic = Diagnostics.File_0_not_found; fileName += ".ts"; diagnosticArgument = [fileName]; } } } if (diagnostic) { if (refFile !== undefined && refEnd !== undefined && refPos !== undefined) { fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, diagnostic, ...diagnosticArgument)); } else { fileProcessingDiagnostics.add(createCompilerDiagnostic(diagnostic, ...diagnosticArgument)); } } } function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFileName: string, refFile: SourceFile, refPos: number, refEnd: number): void { if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, existingFileName)); } else { fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, existingFileName)); } } // Get source file from normalized fileName function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, isReference: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile { if (filesByName.contains(path)) { const file = filesByName.get(path); // try to check if we've already seen this file but with a different casing in path // NOTE: this only makes sense for case-insensitive file systems if (file && options.forceConsistentCasingInFileNames && getNormalizedAbsolutePath(file.fileName, currentDirectory) !== getNormalizedAbsolutePath(fileName, currentDirectory)) { reportFileNamesDifferOnlyInCasingError(fileName, file.fileName, refFile, refPos, refEnd); } // See if we need to reprocess the imports due to prior skipped imports if (file && lookUp(modulesWithElidedImports, file.path)) { if (currentNodeModulesJsDepth < maxNodeModulesJsDepth) { modulesWithElidedImports[file.path] = false; processImportedModules(file, getDirectoryPath(fileName)); } } return file; } // 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) { fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); } else { fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); } }); filesByName.set(path, file); if (file) { file.path = path; if (host.useCaseSensitiveFileNames()) { // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case const existingFile = filesByNameIgnoreCase.get(path); if (existingFile) { reportFileNamesDifferOnlyInCasingError(fileName, existingFile.fileName, refFile, refPos, refEnd); } else { filesByNameIgnoreCase.set(path, file); } } skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib; const basePath = getDirectoryPath(fileName); if (!options.noResolve) { processReferencedFiles(file, basePath, isDefaultLib); processTypeReferenceDirectives(file); } // always process imported modules to record module name resolutions processImportedModules(file, basePath); if (isDefaultLib) { files.unshift(file); } else { files.push(file); } } return file; } function processReferencedFiles(file: SourceFile, basePath: string, isDefaultLib: boolean) { forEach(file.referencedFiles, ref => { const referencedFileName = resolveTripleslashReference(ref.fileName, file.fileName); processSourceFile(referencedFileName, isDefaultLib, /*isReference*/ true, file, ref.pos, ref.end); }); } function processTypeReferenceDirectives(file: SourceFile) { const typeDirectives = map(file.typeReferenceDirectives, l => l.fileName); const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.fileName); for (let i = 0; i < typeDirectives.length; i++) { const ref = file.typeReferenceDirectives[i]; const resolvedTypeReferenceDirective = resolutions[i]; // store resolved type directive on the file setResolvedTypeReferenceDirective(file, ref.fileName, resolvedTypeReferenceDirective); processTypeReferenceDirective(ref.fileName, resolvedTypeReferenceDirective, file, ref.pos, ref.end); } } function processTypeReferenceDirective(typeReferenceDirective: string, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective, refFile?: SourceFile, refPos?: number, refEnd?: number): void { // If we already found this library as a primary reference - nothing to do const previousResolution = resolvedTypeReferenceDirectives[typeReferenceDirective]; if (previousResolution && previousResolution.primary) { return; } let saveResolution = true; if (resolvedTypeReferenceDirective) { if (resolvedTypeReferenceDirective.primary) { // resolved from the primary path processSourceFile(resolvedTypeReferenceDirective.resolvedFileName, /*isDefaultLib*/ false, /*isReference*/ true, refFile, refPos, refEnd); } else { // If we already resolved to this file, it must have been a secondary reference. Check file contents // for sameness and possibly issue an error if (previousResolution) { const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName); if (otherFileText !== getSourceFile(previousResolution.resolvedFileName).text) { fileProcessingDiagnostics.add(createDiagnostic(refFile, refPos, refEnd, Diagnostics.Conflicting_library_definitions_for_0_found_at_1_and_2_Copy_the_correct_file_to_the_typings_folder_to_resolve_this_conflict, typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName )); } // don't overwrite previous resolution result saveResolution = false; } else { // First resolution of this library processSourceFile(resolvedTypeReferenceDirective.resolvedFileName, /*isDefaultLib*/ false, /*isReference*/ true, refFile, refPos, refEnd); } } } else { fileProcessingDiagnostics.add(createDiagnostic(refFile, refPos, refEnd, Diagnostics.Cannot_find_type_definition_file_for_0, typeReferenceDirective)); } if (saveResolution) { resolvedTypeReferenceDirectives[typeReferenceDirective] = resolvedTypeReferenceDirective; } } function createDiagnostic(refFile: SourceFile, refPos: number, refEnd: number, message: DiagnosticMessage, ...args: any[]): Diagnostic { if (refFile === undefined || refPos === undefined || refEnd === undefined) { return createCompilerDiagnostic(message, ...args); } else { return createFileDiagnostic(refFile, refPos, refEnd - refPos, message, ...args); } } function getCanonicalFileName(fileName: string): string { return host.getCanonicalFileName(fileName); } function processImportedModules(file: SourceFile, basePath: string) { collectExternalModuleReferences(file); if (file.imports.length || file.moduleAugmentations.length) { file.resolvedModules = {}; const moduleNames = map(concatenate(file.imports, file.moduleAugmentations), getTextOfLiteral); const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory)); for (let i = 0; i < moduleNames.length; i++) { const resolution = resolutions[i]; setResolvedModule(file, moduleNames[i], resolution); const resolvedPath = resolution ? toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName) : undefined; // add file to program only if: // - resolution was successful // - noResolve is falsy // - module name comes from the list of imports // - it's not a top level JavaScript module that exceeded the search max const isJsFileUnderNodeModules = resolution && resolution.isExternalLibraryImport && hasJavaScriptFileExtension(resolution.resolvedFileName); if (isJsFileUnderNodeModules) { jsFilesFoundSearchingNodeModules[resolvedPath] = true; currentNodeModulesJsDepth++; } const elideImport = isJsFileUnderNodeModules && currentNodeModulesJsDepth > maxNodeModulesJsDepth; const shouldAddFile = resolution && !options.noResolve && i < file.imports.length && !elideImport; if (elideImport) { modulesWithElidedImports[file.path] = true; } else if (shouldAddFile) { findSourceFile(resolution.resolvedFileName, resolvedPath, /*isDefaultLib*/ false, /*isReference*/ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end); } if (isJsFileUnderNodeModules) { currentNodeModulesJsDepth--; } } } else { // no imports - drop cached module resolutions file.resolvedModules = undefined; } return; } function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string { const fileNames: string[] = []; for (const file of sourceFiles) { if (!file.isDeclarationFile) { fileNames.push(file.fileName); } } return computeCommonSourceDirectoryOfFilenames(fileNames, currentDirectory, getCanonicalFileName); } function checkSourceFilesBelongToPath(sourceFiles: SourceFile[], rootDirectory: string): boolean { let allFilesBelongToPath = true; if (sourceFiles) { const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); for (const sourceFile of sourceFiles) { if (!isDeclarationFile(sourceFile)) { const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { programDiagnostics.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 allFilesBelongToPath; } function verifyCompilerOptions() { if (options.isolatedModules) { if (options.declaration) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declaration", "isolatedModules")); } if (options.noEmitOnError) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmitOnError", "isolatedModules")); } if (options.out) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules")); } if (options.outFile) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules")); } } if (options.inlineSourceMap) { if (options.sourceMap) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap")); } if (options.mapRoot) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap")); } } if (options.paths && options.baseUrl === undefined) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_paths_cannot_be_used_without_specifying_baseUrl_option)); } if (options.paths) { for (const key in options.paths) { if (!hasProperty(options.paths, key)) { continue; } if (!hasZeroOrOneAsteriskCharacter(key)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key)); } if (isArray(options.paths[key])) { for (const subst of options.paths[key]) { const typeOfSubst = typeof subst; if (typeOfSubst === "string") { if (!hasZeroOrOneAsteriskCharacter(subst)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitution_0_in_pattern_1_in_can_have_at_most_one_Asterisk_character, subst, key)); } } else { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst)); } } } else { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key)); } } } if (!options.sourceMap && !options.inlineSourceMap) { if (options.inlineSources) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources")); } if (options.sourceRoot) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot")); } } if (options.out && options.outFile) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile")); } if (options.mapRoot && !options.sourceMap) { // Error to specify --mapRoot without --sourcemap programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "mapRoot", "sourceMap")); } if (options.declarationDir) { if (!options.declaration) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "declarationDir", "declaration")); } if (options.out || options.outFile) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile")); } } if (options.lib && options.noLib) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib")); } const languageVersion = options.target || ScriptTarget.ES3; const outFile = options.outFile || options.out; const firstExternalModuleSourceFile = forEach(files, f => isExternalModule(f) ? f : undefined); if (options.isolatedModules) { if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES6) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher)); } const firstNonExternalModuleSourceFile = forEach(files, f => !isExternalModule(f) && !isDeclarationFile(f) ? f : undefined); if (firstNonExternalModuleSourceFile) { const span = getErrorSpanForNode(firstNonExternalModuleSourceFile, firstNonExternalModuleSourceFile); programDiagnostics.add(createFileDiagnostic(firstNonExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_namespaces_when_the_isolatedModules_flag_is_provided)); } } else if (firstExternalModuleSourceFile && languageVersion < ScriptTarget.ES6 && options.module === ModuleKind.None) { // We cannot use createDiagnosticFromNode because nodes do not have parents yet const span = getErrorSpanForNode(firstExternalModuleSourceFile, firstExternalModuleSourceFile.externalModuleIndicator); programDiagnostics.add(createFileDiagnostic(firstExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); } // Cannot specify module gen that isn't amd or system with --out if (outFile) { if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile")); } else if (options.module === undefined && firstExternalModuleSourceFile) { const span = getErrorSpanForNode(firstExternalModuleSourceFile, firstExternalModuleSourceFile.externalModuleIndicator); programDiagnostics.add(createFileDiagnostic(firstExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, options.out ? "out" : "outFile")); } } // there has to be common source directory if user specified --outdir || --sourceRoot // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted if (options.outDir || // there is --outDir specified options.sourceRoot || // there is --sourceRoot specified options.mapRoot) { // there is --mapRoot specified // Precalculate and cache the common source directory const dir = getCommonSourceDirectory(); // If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure if (options.outDir && dir === "" && forEach(files, file => getRootLength(file.fileName) > 1)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files)); } } if (!options.noEmit && options.allowJs && options.declaration) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration")); } if (options.emitDecoratorMetadata && !options.experimentalDecorators) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators")); } if (options.reactNamespace && !isIdentifier(options.reactNamespace, languageVersion)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace)); } // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files if (!options.noEmit && !options.suppressOutputPathCheck) { const emitHost = getEmitHost(); const emitFilesSeen = createFileMap(!host.useCaseSensitiveFileNames() ? key => key.toLocaleLowerCase() : undefined); forEachExpectedEmitFile(emitHost, (emitFileNames, sourceFiles, isBundledEmit) => { verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); }); } // Verify that all the emit files are unique and don't overwrite input files function verifyEmitFilePath(emitFileName: string, emitFilesSeen: FileMap) { if (emitFileName) { const emitFilePath = toPath(emitFileName, currentDirectory, getCanonicalFileName); // Report error if the output overwrites input file if (filesByName.contains(emitFilePath)) { createEmitBlockingDiagnostics(emitFileName, emitFilePath, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file); } // Report error if multiple files write into same file if (emitFilesSeen.contains(emitFilePath)) { // Already seen the same emit file - report error createEmitBlockingDiagnostics(emitFileName, emitFilePath, Diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files); } else { emitFilesSeen.set(emitFilePath, true); } } } } function createEmitBlockingDiagnostics(emitFileName: string, emitFilePath: Path, message: DiagnosticMessage) { hasEmitBlockingDiagnostics.set(toPath(emitFileName, currentDirectory, getCanonicalFileName), true); programDiagnostics.add(createCompilerDiagnostic(message, emitFileName)); } } }