1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-01 09:24:20 -04:00
denoland-deno/cli/js/compiler_imports.ts
Kitson Kelly 6431622a6d
fix: mis-detecting imports on JavaScript when there is no checkJs (#4040)
This PR fixes an issue where we recursively analysed imports on plain JS files
in the compiler irrespective of "checkJs" being true. This caused problems
where when analysing the imports of those files, we would mistake some
import like structures (AMD/CommonJS) as dependencies and try to resolve
the "modules" even though the compiler would not actually look at those files.
2020-02-19 22:58:05 -05:00

190 lines
5.7 KiB
TypeScript

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import {
MediaType,
SourceFile,
SourceFileJson
} from "./compiler_sourcefile.ts";
import { cwd } from "./dir.ts";
import * as dispatch from "./dispatch.ts";
import { sendAsync, sendSync } from "./dispatch_json.ts";
import { assert } from "./util.ts";
import * as util from "./util.ts";
/** Resolve a path to the final path segment passed. */
function resolvePath(...pathSegments: string[]): string {
let resolvedPath = "";
let resolvedAbsolute = false;
for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
let path: string;
if (i >= 0) path = pathSegments[i];
else path = cwd();
// Skip empty entries
if (path.length === 0) {
continue;
}
resolvedPath = `${path}/${resolvedPath}`;
resolvedAbsolute = path.charCodeAt(0) === util.CHAR_FORWARD_SLASH;
}
// At this point the path should be resolved to a full absolute path, but
// handle relative paths to be safe (might happen when cwd() fails)
// Normalize the path
resolvedPath = util.normalizeString(
resolvedPath,
!resolvedAbsolute,
"/",
code => code === util.CHAR_FORWARD_SLASH
);
if (resolvedAbsolute) {
if (resolvedPath.length > 0) return `/${resolvedPath}`;
else return "/";
} else if (resolvedPath.length > 0) return resolvedPath;
else return ".";
}
/** Resolve a relative specifier based on the referrer. Used when resolving
* modules internally within the runtime compiler API. */
function resolveSpecifier(specifier: string, referrer: string): string {
if (!specifier.startsWith(".")) {
return specifier;
}
const pathParts = referrer.split("/");
pathParts.pop();
let path = pathParts.join("/");
path = path.endsWith("/") ? path : `${path}/`;
return resolvePath(path, specifier);
}
/** Ops to Rust to resolve modules' URLs. */
export function resolveModules(
specifiers: string[],
referrer?: string
): string[] {
util.log("compiler_imports::resolveModules", { specifiers, referrer });
return sendSync(dispatch.OP_RESOLVE_MODULES, { specifiers, referrer });
}
/** Ops to Rust to fetch modules meta data. */
function fetchSourceFiles(
specifiers: string[],
referrer?: string
): Promise<SourceFileJson[]> {
util.log("compiler_imports::fetchSourceFiles", { specifiers, referrer });
return sendAsync(dispatch.OP_FETCH_SOURCE_FILES, {
specifiers,
referrer
});
}
/** Given a filename, determine the media type based on extension. Used when
* resolving modules internally in a runtime compile. */
function getMediaType(filename: string): MediaType {
const maybeExtension = /\.([a-zA-Z]+)$/.exec(filename);
if (!maybeExtension) {
util.log(`!!! Could not identify valid extension: "${filename}"`);
return MediaType.Unknown;
}
const [, extension] = maybeExtension;
switch (extension.toLowerCase()) {
case "js":
return MediaType.JavaScript;
case "jsx":
return MediaType.JSX;
case "json":
return MediaType.Json;
case "ts":
return MediaType.TypeScript;
case "tsx":
return MediaType.TSX;
case "wasm":
return MediaType.Wasm;
default:
util.log(`!!! Unknown extension: "${extension}"`);
return MediaType.Unknown;
}
}
/** Recursively process the imports of modules from within the supplied sources,
* generating `SourceFile`s of any imported files.
*
* Specifiers are supplied in an array of tuples where the first is the
* specifier that will be requested in the code and the second is the specifier
* that should be actually resolved. */
export function processLocalImports(
sources: Record<string, string>,
specifiers: Array<[string, string]>,
referrer?: string,
checkJs = false
): string[] {
if (!specifiers.length) {
return [];
}
const moduleNames = specifiers.map(
referrer
? ([, specifier]): string => resolveSpecifier(specifier, referrer)
: ([, specifier]): string => specifier
);
for (let i = 0; i < moduleNames.length; i++) {
const moduleName = moduleNames[i];
assert(moduleName in sources, `Missing module in sources: "${moduleName}"`);
const sourceFile =
SourceFile.get(moduleName) ||
new SourceFile({
url: moduleName,
filename: moduleName,
sourceCode: sources[moduleName],
mediaType: getMediaType(moduleName)
});
sourceFile.cache(specifiers[i][0], referrer);
if (!sourceFile.processed) {
processLocalImports(
sources,
sourceFile.imports(checkJs),
sourceFile.url,
checkJs
);
}
}
return moduleNames;
}
/** Recursively process the imports of modules, generating `SourceFile`s of any
* imported files.
*
* Specifiers are supplied in an array of tuples where the first is the
* specifier that will be requested in the code and the second is the specifier
* that should be actually resolved. */
export async function processImports(
specifiers: Array<[string, string]>,
referrer?: string,
checkJs = false
): Promise<string[]> {
if (!specifiers.length) {
return [];
}
const sources = specifiers.map(([, moduleSpecifier]) => moduleSpecifier);
const resolvedSources = resolveModules(sources, referrer);
const sourceFiles = await fetchSourceFiles(resolvedSources, referrer);
assert(sourceFiles.length === specifiers.length);
for (let i = 0; i < sourceFiles.length; i++) {
const sourceFileJson = sourceFiles[i];
const sourceFile =
SourceFile.get(sourceFileJson.url) || new SourceFile(sourceFileJson);
sourceFile.cache(specifiers[i][0], referrer);
if (!sourceFile.processed) {
await processImports(
sourceFile.imports(checkJs),
sourceFile.url,
checkJs
);
}
}
return resolvedSources;
}