Load JS from node_modules

This commit is contained in:
Bill Ticehurst 2016-02-13 07:48:25 -08:00
parent c7fcd0204c
commit dfb0dcde0e
5 changed files with 67 additions and 23 deletions

View file

@ -326,6 +326,11 @@ namespace ts {
name: "noImplicitUseStrict",
type: "boolean",
description: Diagnostics.Do_not_emit_use_strict_directives_in_module_output
},
{
name: "maxNodeModuleJsDepth",
type: "number",
description: Diagnostics.The_maximum_dependency_depth_to_search_under_node_modules_and_load_JavaScript_files
}
];

View file

@ -2576,6 +2576,10 @@
"category": "Message",
"code": 6112
},
"The maximum dependency depth to search under node_modules and load JavaScript files": {
"category": "Message",
"code": 6113
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",

View file

@ -9,10 +9,10 @@ namespace ts {
/* @internal */ export let ioWriteTime = 0;
/** The version of the TypeScript compiler release */
export const version = "1.9.0";
const emptyArray: any[] = [];
export const version = "1.9.0";
const startsWithDotSlashOrDotDotSlash = /^(\.\/|\.\.\/)/;
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string {
let fileName = "tsconfig.json";
@ -79,9 +79,7 @@ namespace ts {
return false;
}
const i = moduleName.lastIndexOf("./", 1);
const startsWithDotSlashOrDotDotSlash = i === 0 || (i === 1 && moduleName.charCodeAt(0) === CharacterCodes.dot);
return !startsWithDotSlashOrDotDotSlash;
return !startsWithDotSlashOrDotDotSlash.test(moduleName);
}
interface ModuleResolutionState {
@ -448,11 +446,11 @@ namespace ts {
trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath);
}
let jsonContent: { typings?: string };
let jsonContent: { typings?: string; main?: string };
try {
const jsonText = state.host.readFile(packageJsonPath);
jsonContent = jsonText ? <{ typings?: string }>JSON.parse(jsonText) : { typings: undefined };
jsonContent = jsonText ? <{ typings?: string; main?: string }>JSON.parse(jsonText) : { typings: undefined, main: undefined };
}
catch (e) {
// gracefully handle if readFile fails or returns not JSON
@ -465,7 +463,7 @@ namespace ts {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_typings_field_0_that_references_1, jsonContent.typings, typingsFile);
}
const result = loadModuleFromFile(typingsFile, extensions, failedLookupLocation, !directoryProbablyExists(getDirectoryPath(typingsFile), state.host), state);
const result = loadModuleFromFile(typingsFile, /* don't add extension */ [""], failedLookupLocation, !directoryProbablyExists(getDirectoryPath(typingsFile), state.host), state);
if (result) {
return result;
}
@ -479,6 +477,15 @@ namespace ts {
trace(state.host, Diagnostics.package_json_does_not_have_typings_field);
}
}
// TODO (billti): tracing as per above
if (typeof jsonContent.main === "string") {
// If 'main' points to 'foo.js', we still want to try and load 'foo.d.ts' and 'foo.ts' first (and only 'foo.js' if 'allowJs' is set).
const mainFile = normalizePath(combinePaths(candidate, removeFileExtension(jsonContent.main)));
const result = loadModuleFromFile(mainFile, extensions, failedLookupLocation, !directoryProbablyExists(getDirectoryPath(mainFile), state.host), state);
if (result) {
return result;
}
}
}
else {
if (state.traceEnabled) {
@ -499,12 +506,13 @@ namespace ts {
const nodeModulesFolder = combinePaths(directory, "node_modules");
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
// Load only typescript files irrespective of allowJs option if loading from node modules
let result = loadModuleFromFile(candidate, supportedTypeScriptExtensions, failedLookupLocations, !nodeModulesFolderExists, state);
const supportedExtensions = getSupportedExtensions(state.compilerOptions);
let result = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, !nodeModulesFolderExists, state);
if (result) {
return result;
}
result = loadNodeModuleFromDirectory(supportedTypeScriptExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
if (result) {
return result;
}
@ -1397,7 +1405,7 @@ namespace ts {
}
// Get source file from normalized fileName
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number, isFileFromNodeSearch?: boolean): 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
@ -1406,6 +1414,13 @@ namespace ts {
reportFileNamesDifferOnlyInCasingError(fileName, file.fileName, refFile, refPos, refEnd);
}
// If this was a file found by a node_modules search, set the nodeModuleSearchDistance to parent distance + 1.
if (isFileFromNodeSearch) {
const newDistance = (refFile && refFile.nodeModuleSearchDistance) === undefined ? 1 : refFile.nodeModuleSearchDistance + 1;
// If already set on the file, don't overwrite if it was already found closer (which may be '0' if added as a root file)
file.nodeModuleSearchDistance = (typeof file.nodeModuleSearchDistance === "number") ? Math.min(file.nodeModuleSearchDistance, newDistance) : newDistance;
}
return file;
}
@ -1424,6 +1439,12 @@ namespace ts {
if (file) {
file.path = path;
// Default to same distance as parent. Add one if found by a search.
file.nodeModuleSearchDistance = (refFile && refFile.nodeModuleSearchDistance) || 0;
if (isFileFromNodeSearch) {
file.nodeModuleSearchDistance++;
}
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);
@ -1468,11 +1489,13 @@ namespace ts {
}
function processImportedModules(file: SourceFile, basePath: string) {
const maxJsNodeModuleSearchDistance = options.maxNodeModuleJsDepth || 0;
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));
file.nodeModuleSearchDistance = file.nodeModuleSearchDistance || 0;
for (let i = 0; i < moduleNames.length; i++) {
const resolution = resolutions[i];
setResolvedModule(file, moduleNames[i], resolution);
@ -1480,16 +1503,23 @@ namespace ts {
// - resolution was successful
// - noResolve is falsy
// - module name come from the list fo imports
const shouldAddFile = resolution &&
!options.noResolve &&
i < file.imports.length;
// - it's not a top level JavaScript module that exceeded the search max
const exceedsJsSearchDepth = resolution && resolution.isExternalLibraryImport &&
hasJavaScriptFileExtension(resolution.resolvedFileName) &&
file.nodeModuleSearchDistance >= maxJsNodeModuleSearchDistance;
const shouldAddFile = resolution && !options.noResolve && i < file.imports.length && !exceedsJsSearchDepth;
if (shouldAddFile) {
const importedFile = findSourceFile(resolution.resolvedFileName, toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName), /*isDefaultLib*/ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
const importedFile = findSourceFile(resolution.resolvedFileName,
toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName),
/*isDefaultLib*/ false,
file,
skipTrivia(file.text, file.imports[i].pos),
file.imports[i].end,
resolution.isExternalLibraryImport);
if (importedFile && resolution.isExternalLibraryImport) {
// Since currently irrespective of allowJs, we only look for supportedTypeScript extension external module files,
// this check is ok. Otherwise this would be never true for javascript file
// TODO (billti): Should we check here if a JavaScript file is a CommonJS file, or doesn't have /// references?
if (importedFile && resolution.isExternalLibraryImport && !hasJavaScriptFileExtension(importedFile.fileName)) {
if (!isExternalModule(importedFile) && importedFile.statements.length) {
const start = getTokenPosOfNode(file.imports[i], file);
fileProcessingDiagnostics.add(createFileDiagnostic(file, start, file.imports[i].end - start, Diagnostics.Exported_external_package_typings_file_0_is_not_a_module_Please_contact_the_package_author_to_update_the_package_definition, importedFile.fileName));

View file

@ -1536,6 +1536,8 @@ namespace ts {
/* @internal */ externalModuleIndicator: Node;
// The first node that causes this file to be a CommonJS module
/* @internal */ commonJsModuleIndicator: Node;
// The number of times node_modules was searched to locate the package containing this file
/* @internal */ nodeModuleSearchDistance?: number;
/* @internal */ identifiers: Map<string>;
/* @internal */ nodeCount: number;
@ -2419,6 +2421,7 @@ namespace ts {
traceModuleResolution?: boolean;
allowSyntheticDefaultImports?: boolean;
allowJs?: boolean;
maxNodeModuleJsDepth?: number;
noImplicitUseStrict?: boolean;
/* @internal */ stripInternal?: boolean;

View file

@ -2035,7 +2035,8 @@ namespace ts {
else {
const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile];
for (const sourceFile of sourceFiles) {
if (!isDeclarationFile(sourceFile)) {
// Don't emit if source file is a declaration file, or was found by a search under 'node_modules'
if (!isDeclarationFile(sourceFile) && !sourceFile.nodeModuleSearchDistance) {
onSingleFileEmit(host, sourceFile);
}
}
@ -2069,9 +2070,10 @@ namespace ts {
function onBundledEmit(host: EmitHost) {
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
const bundledSources = filter(host.getSourceFiles(),
sourceFile => !isDeclarationFile(sourceFile) && // Not a declaration file
(!isExternalModule(sourceFile) || // non module file
(getEmitModuleKind(options) && isExternalModule(sourceFile)))); // module that can emit - note falsy value from getEmitModuleKind means the module kind that shouldn't be emitted
sourceFile => !isDeclarationFile(sourceFile) && // Not a declaration file
!sourceFile.nodeModuleSearchDistance && // Not loaded from searching under node_modules
(!isExternalModule(sourceFile) || // non module file
(getEmitModuleKind(options) && isExternalModule(sourceFile)))); // module that can emit - note falsy value from getEmitModuleKind means the module kind that shouldn't be emitted
if (bundledSources.length) {
const jsFilePath = options.outFile || options.out;
const emitFileNames: EmitFileNames = {