mirror of
https://github.com/chenasraf/DefinitelyTyped-tools.git
synced 2026-05-17 17:48:07 +00:00
Use real resolution for used file detection (#615)
* Use real TypeScript resolution for determining used files * getTypingInfo needs the whole DT FS * Fix a few things not showing up in tests * Disable superfluous OTHER_FILES error * Replace internal API * Bring back error for relatively referencing other packages * Fix `getLocallyInstalledDefinitelyTyped` * PR suggestions
This commit is contained in:
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"typescript.tsdk": "/Developer/microsoft/TypeScript/built/local"
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { DiskFS, downloadAndExtractFile, LoggerWithErrors, FS, exec } from "@def
|
||||
|
||||
import { dataDirPath, definitelyTypedZipUrl } from "./lib/settings";
|
||||
import { ExecException } from "child_process";
|
||||
import path from "path";
|
||||
|
||||
/** Settings that may be determined dynamically. */
|
||||
export interface ParseDefinitionsOptions {
|
||||
@@ -36,9 +37,9 @@ export async function getDefinitelyTyped(options: ParseDefinitionsOptions, log:
|
||||
throw new Error(`'git diff' should be empty. Following files changed:\n${stdout}`);
|
||||
}
|
||||
log.info(`Using local DefinitelyTyped at ${options.definitelyTypedPath}`);
|
||||
return new DiskFS(`${options.definitelyTypedPath}/`);
|
||||
return new DiskFS(`${path.resolve(options.definitelyTypedPath)}/`);
|
||||
}
|
||||
|
||||
export function getLocallyInstalledDefinitelyTyped(path: string): FS {
|
||||
return new DiskFS(`${path}/`);
|
||||
export function getLocallyInstalledDefinitelyTyped(definitelyTypedPath: string): FS {
|
||||
return new DiskFS(`${path.resolve(definitelyTypedPath)}/`);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import process = require("process");
|
||||
import { logUncaughtErrors } from "@definitelytyped/utils";
|
||||
import { getLocallyInstalledDefinitelyTyped } from "../get-definitely-typed";
|
||||
import { getTypingInfo } from "./definition-parser";
|
||||
import { dirname } from "path";
|
||||
|
||||
// This file is "called" by runWithChildProcesses from parse-definition.ts
|
||||
export const definitionParserWorkerFilename = __filename;
|
||||
@@ -16,7 +17,7 @@ if (!module.parent) {
|
||||
for (const packageName of message as readonly string[]) {
|
||||
const data = await getTypingInfo(
|
||||
packageName,
|
||||
getLocallyInstalledDefinitelyTyped(typesPath).subDir(packageName)
|
||||
getLocallyInstalledDefinitelyTyped(dirname(typesPath))
|
||||
);
|
||||
process.send!({ data, packageName });
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
TypingsDataRaw,
|
||||
TypingsVersionsRaw,
|
||||
DirectoryParsedTypingVersion,
|
||||
getMangledNameForScopedPackage,
|
||||
} from "../packages";
|
||||
import { getAllowedPackageJsonDependencies } from "./settings";
|
||||
import {
|
||||
@@ -27,8 +28,10 @@ import {
|
||||
removeVersionFromPackageName,
|
||||
hasVersionNumberInMapping,
|
||||
mangleScopedPackage,
|
||||
createModuleResolutionHost,
|
||||
} from "@definitelytyped/utils";
|
||||
import { TypeScriptVersion } from "@definitelytyped/typescript-versions";
|
||||
import path from "path";
|
||||
|
||||
function matchesVersion(
|
||||
typingsDataRaw: TypingsDataRaw,
|
||||
@@ -47,8 +50,7 @@ function formattedLibraryVersion(typingsDataRaw: TypingsDataRaw): `${number}.${n
|
||||
return `${typingsDataRaw.libraryMajorVersion}.${typingsDataRaw.libraryMinorVersion}`;
|
||||
}
|
||||
|
||||
/** @param fs Rooted at the package's directory, e.g. `DefinitelyTyped/types/abs` */
|
||||
export async function getTypingInfo(packageName: string, fs: FS): Promise<TypingsVersionsRaw> {
|
||||
export async function getTypingInfo(packageName: string, dt: FS): Promise<TypingsVersionsRaw> {
|
||||
if (packageName !== packageName.toLowerCase()) {
|
||||
throw new Error(`Package name \`${packageName}\` should be strictly lowercase`);
|
||||
}
|
||||
@@ -56,6 +58,8 @@ export async function getTypingInfo(packageName: string, fs: FS): Promise<Typing
|
||||
readonly directoryName: string;
|
||||
readonly version: DirectoryParsedTypingVersion;
|
||||
}
|
||||
|
||||
const fs = dt.subDir("types").subDir(getMangledNameForScopedPackage(packageName));
|
||||
const [rootDirectoryLs, olderVersionDirectories] = split<string, OlderVersionDir>(
|
||||
fs.readdir(),
|
||||
(fileOrDirectoryName) => {
|
||||
@@ -64,11 +68,12 @@ export async function getTypingInfo(packageName: string, fs: FS): Promise<Typing
|
||||
}
|
||||
);
|
||||
|
||||
const moduleResolutionHost = createModuleResolutionHost(dt);
|
||||
const considerLibraryMinorVersion = olderVersionDirectories.some(({ version }) => version.minor !== undefined);
|
||||
|
||||
const latestData: TypingsDataRaw = {
|
||||
libraryVersionDirectoryName: undefined,
|
||||
...(await combineDataForAllTypesVersions(packageName, rootDirectoryLs, fs, undefined)),
|
||||
...(await combineDataForAllTypesVersions(packageName, rootDirectoryLs, fs, undefined, moduleResolutionHost)),
|
||||
};
|
||||
|
||||
const older = await Promise.all(
|
||||
@@ -77,9 +82,9 @@ export async function getTypingInfo(packageName: string, fs: FS): Promise<Typing
|
||||
const latest = `${latestData.libraryMajorVersion}.${latestData.libraryMinorVersion}`;
|
||||
throw new Error(
|
||||
`The latest version of the '${packageName}' package is ${latest}, so the subdirectory '${directoryName}' is not allowed` +
|
||||
(`v${latest}` === directoryName
|
||||
? "."
|
||||
: `; since it applies to any ${latestData.libraryMajorVersion}.* version, up to and including ${latest}.`)
|
||||
(`v${latest}` === directoryName
|
||||
? "."
|
||||
: `; since it applies to any ${latestData.libraryMajorVersion}.* version, up to and including ${latest}.`)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,19 +92,19 @@ export async function getTypingInfo(packageName: string, fs: FS): Promise<Typing
|
||||
const ls = fs.readdir(directoryName);
|
||||
const data: TypingsDataRaw = {
|
||||
libraryVersionDirectoryName: formatTypingVersion(directoryVersion),
|
||||
...(await combineDataForAllTypesVersions(packageName, ls, fs.subDir(directoryName), directoryVersion)),
|
||||
...(await combineDataForAllTypesVersions(packageName, ls, fs.subDir(directoryName), directoryVersion, moduleResolutionHost)),
|
||||
};
|
||||
|
||||
if (!matchesVersion(data, directoryVersion, considerLibraryMinorVersion)) {
|
||||
if (considerLibraryMinorVersion) {
|
||||
throw new Error(
|
||||
`Directory ${directoryName} indicates major.minor version ${directoryVersion.major}.${directoryVersion.minor}, ` +
|
||||
`but header indicates major.minor version ${data.libraryMajorVersion}.${data.libraryMinorVersion}`
|
||||
`but header indicates major.minor version ${data.libraryMajorVersion}.${data.libraryMinorVersion}`
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
`Directory ${directoryName} indicates major version ${directoryVersion.major}, but header indicates major version ` +
|
||||
data.libraryMajorVersion.toString()
|
||||
data.libraryMajorVersion.toString()
|
||||
);
|
||||
}
|
||||
return data;
|
||||
@@ -193,9 +198,20 @@ async function combineDataForAllTypesVersions(
|
||||
typingsPackageName: string,
|
||||
ls: readonly string[],
|
||||
fs: FS,
|
||||
directoryVersion: DirectoryParsedTypingVersion | undefined
|
||||
directoryVersion: DirectoryParsedTypingVersion | undefined,
|
||||
moduleResolutionHost: ts.ModuleResolutionHost,
|
||||
): Promise<Omit<TypingsDataRaw, "libraryVersionDirectoryName">> {
|
||||
const { remainingLs, typesVersions, hasPackageJson } = getTypesVersionsAndPackageJson(ls);
|
||||
const packageJson = hasPackageJson
|
||||
? (fs.readJson(packageJsonName) as {
|
||||
readonly license?: unknown;
|
||||
readonly dependencies?: unknown;
|
||||
readonly imports?: unknown;
|
||||
readonly exports?: unknown;
|
||||
readonly type?: unknown;
|
||||
})
|
||||
: {};
|
||||
const packageJsonType = checkPackageJsonType(packageJson.type, packageJsonName);
|
||||
|
||||
// Every typesVersion has an index.d.ts, but only the root index.d.ts should have a header.
|
||||
const {
|
||||
@@ -210,33 +226,23 @@ async function combineDataForAllTypesVersions(
|
||||
const dataForRoot = getTypingDataForSingleTypesVersion(
|
||||
undefined,
|
||||
typingsPackageName,
|
||||
fs.debugPath(),
|
||||
remainingLs,
|
||||
fs,
|
||||
directoryVersion
|
||||
directoryVersion,
|
||||
moduleResolutionHost,
|
||||
);
|
||||
const dataForOtherTypesVersions = typesVersions.map((tsVersion) => {
|
||||
const subFs = fs.subDir(`ts${tsVersion}`);
|
||||
return getTypingDataForSingleTypesVersion(
|
||||
tsVersion,
|
||||
typingsPackageName,
|
||||
fs.debugPath(),
|
||||
subFs.readdir(),
|
||||
subFs,
|
||||
directoryVersion
|
||||
directoryVersion,
|
||||
moduleResolutionHost,
|
||||
);
|
||||
});
|
||||
const allTypesVersions = [dataForRoot, ...dataForOtherTypesVersions];
|
||||
|
||||
const packageJson = hasPackageJson
|
||||
? (fs.readJson(packageJsonName) as {
|
||||
readonly license?: unknown;
|
||||
readonly dependencies?: unknown;
|
||||
readonly imports?: unknown;
|
||||
readonly exports?: unknown;
|
||||
readonly type?: unknown;
|
||||
})
|
||||
: {};
|
||||
const license = getLicenseFromPackageJson(packageJson.license);
|
||||
const packageJsonDependencies = await checkPackageJsonDependencies(packageJson.dependencies, packageJsonName);
|
||||
|
||||
@@ -271,7 +277,7 @@ async function combineDataForAllTypesVersions(
|
||||
declaredModules: getAllUniqueValues<"declaredModules", string>(allTypesVersions, "declaredModules"),
|
||||
imports: checkPackageJsonImports(packageJson.imports, packageJsonName),
|
||||
exports: checkPackageJsonExportsAndAddPJsonEntry(packageJson.exports, packageJsonName),
|
||||
type: checkPackageJsonType(packageJson.type, packageJsonName),
|
||||
type: packageJsonType,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -300,26 +306,36 @@ interface TypingDataFromIndividualTypeScriptVersion {
|
||||
function getTypingDataForSingleTypesVersion(
|
||||
typescriptVersion: TypeScriptVersion | undefined,
|
||||
packageName: string,
|
||||
packageDirectory: string,
|
||||
ls: readonly string[],
|
||||
fs: FS,
|
||||
directoryVersion: DirectoryParsedTypingVersion | undefined
|
||||
directoryVersion: DirectoryParsedTypingVersion | undefined,
|
||||
moduleResolutionHost: ts.ModuleResolutionHost,
|
||||
): TypingDataFromIndividualTypeScriptVersion {
|
||||
const tsconfig = fs.readJson("tsconfig.json") as TsConfig;
|
||||
const configHost: ts.ParseConfigHost = {
|
||||
...moduleResolutionHost,
|
||||
readDirectory: dir => fs.readdir(dir),
|
||||
useCaseSensitiveFileNames: true,
|
||||
};
|
||||
|
||||
const compilerOptions = ts.parseJsonConfigFileContent(tsconfig, configHost, path.resolve("/", fs.debugPath())).options;
|
||||
checkFilesFromTsConfig(packageName, tsconfig, fs.debugPath());
|
||||
|
||||
const { types, tests, hasNonRelativeImports } = allReferencedFiles(
|
||||
tsconfig.files!,
|
||||
fs,
|
||||
packageName,
|
||||
packageDirectory
|
||||
moduleResolutionHost,
|
||||
compilerOptions,
|
||||
);
|
||||
|
||||
const usedFiles = new Set([...types.keys(), ...tests.keys(), "tsconfig.json", "tslint.json"]);
|
||||
const otherFiles = ls.includes(unusedFilesName)
|
||||
? fs
|
||||
// tslint:disable-next-line:non-literal-fs-path -- Not a reference to the fs package
|
||||
.readFile(unusedFilesName)
|
||||
.split(/\r?\n/g)
|
||||
.filter(Boolean)
|
||||
// tslint:disable-next-line:non-literal-fs-path -- Not a reference to the fs package
|
||||
.readFile(unusedFilesName)
|
||||
.split(/\r?\n/g)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
if (ls.includes(unusedFilesName) && !otherFiles.length) {
|
||||
throw new Error(`In ${packageName}: OTHER_FILES.txt is empty.`);
|
||||
@@ -336,7 +352,11 @@ function getTypingDataForSingleTypesVersion(
|
||||
)) {
|
||||
// add d.ts files from OTHER_FILES.txt in order get their dependencies
|
||||
// tslint:disable-next-line:non-literal-fs-path -- Not a reference to the fs package
|
||||
types.set(untestedTypeFile, createSourceFile(untestedTypeFile, fs.readFile(untestedTypeFile)));
|
||||
types.set(untestedTypeFile, createSourceFile(
|
||||
untestedTypeFile,
|
||||
fs.readFile(untestedTypeFile),
|
||||
moduleResolutionHost,
|
||||
compilerOptions));
|
||||
}
|
||||
|
||||
const { dependencies: dependenciesWithDeclaredModules, globals, declaredModules } = getModuleInfo(packageName, types);
|
||||
@@ -348,7 +368,7 @@ function getTypingDataForSingleTypesVersion(
|
||||
.map((m) => rootName(m, types, packageName))
|
||||
.filter((dependency) => dependency !== packageName)
|
||||
);
|
||||
const testDependencies = [...getTestDependencies(packageName, tests.keys(), dependenciesSet, fs)]
|
||||
const testDependencies = [...getTestDependencies(packageName, tests.keys(), dependenciesSet, fs, moduleResolutionHost, compilerOptions)]
|
||||
.filter((m) => !declaredModulesSet.has(m))
|
||||
.map((m) => rootName(m, types, packageName))
|
||||
.filter((dependency) => dependency !== packageName);
|
||||
@@ -514,7 +534,7 @@ Other d.ts files must either be referenced through index.d.ts, tests, or added t
|
||||
file.endsWith(".ts") || file.endsWith(".tsx")
|
||||
? `Expected file '${file}' to be named '${expectedName}' or to be inside a '${directoryPath}/test/' directory`
|
||||
: `Unexpected file extension for '${file}' -- expected '.ts' or '.tsx' (maybe this should not be in "files", but ` +
|
||||
"OTHER_FILES.txt)";
|
||||
"OTHER_FILES.txt)";
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
@@ -773,9 +793,12 @@ function checkAllUsedRecur(ls: Iterable<string>, usedFiles: Set<string>, unusedF
|
||||
|
||||
for (const unusedFile of unusedFiles) {
|
||||
if (usedFiles.has(unusedFile)) {
|
||||
throw new Error(
|
||||
`File ${fs.debugPath()}/${unusedFile} listed in ${unusedFilesName} is already reachable from tsconfig.json.`
|
||||
);
|
||||
// TODO: reenable after fixing up DT
|
||||
console.log(`File ${fs.debugPath()}/${unusedFile} listed in ${unusedFilesName} is already reachable from tsconfig.json.`);
|
||||
return;
|
||||
// throw new Error(
|
||||
// `File ${fs.debugPath()}/${unusedFile} listed in ${unusedFilesName} is already reachable from tsconfig.json.`
|
||||
// );
|
||||
}
|
||||
throw new Error(`File ${fs.debugPath()}/${unusedFile} listed in ${unusedFilesName} does not exist.`);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert = require("assert");
|
||||
import * as path from "path";
|
||||
import * as ts from "typescript";
|
||||
import { sort, joinPaths, FS, normalizeSlashes, hasWindowsSlashes } from "@definitelytyped/utils";
|
||||
import { sort, joinPaths, FS, hasWindowsSlashes, assertDefined } from "@definitelytyped/utils";
|
||||
|
||||
import { readFileAndThrowOnBOM } from "./definition-parser";
|
||||
import { getMangledNameForScopedPackage } from "../packages";
|
||||
@@ -19,7 +19,7 @@ export function getModuleInfo(packageName: string, all: Map<string, ts.SourceFil
|
||||
|
||||
for (const sourceFile of all.values()) {
|
||||
for (const ref of imports(sourceFile)) {
|
||||
addDependency(ref);
|
||||
addDependency(ref.text);
|
||||
}
|
||||
for (const ref of sourceFile.typeReferenceDirectives) {
|
||||
addDependency(ref.fileName);
|
||||
@@ -130,72 +130,97 @@ export function allReferencedFiles(
|
||||
entryFilenames: readonly string[],
|
||||
fs: FS,
|
||||
packageName: string,
|
||||
baseDirectory: string
|
||||
moduleResolutionHost: ts.ModuleResolutionHost,
|
||||
compilerOptions: ts.CompilerOptions,
|
||||
): { types: Map<string, ts.SourceFile>; tests: Map<string, ts.SourceFile>; hasNonRelativeImports: boolean } {
|
||||
const seenReferences = new Set<string>();
|
||||
const types = new Map<string, ts.SourceFile>();
|
||||
const tests = new Map<string, ts.SourceFile>();
|
||||
// The directory where the tsconfig/index.d.ts is - i.e., may be a version within the package
|
||||
const baseDirectory = path.resolve("/", fs.debugPath());
|
||||
// The root of the package and all versions, i.e., the direct subdirectory of types/
|
||||
const packageDirectory = baseDirectory.slice(0, baseDirectory.lastIndexOf(`types/${getMangledNameForScopedPackage(packageName)}`) + `types/${getMangledNameForScopedPackage(packageName)}`.length);
|
||||
let hasNonRelativeImports = false;
|
||||
entryFilenames.forEach((text) => recur({ text, exact: true }));
|
||||
entryFilenames.forEach((fileName) => recur(undefined, { text: fileName, kind: "path" }));
|
||||
return { types, tests, hasNonRelativeImports };
|
||||
|
||||
function recur({ text, exact }: Reference): void {
|
||||
const resolvedFilename = exact ? text : resolveModule(text, fs);
|
||||
if (seenReferences.has(resolvedFilename)) {
|
||||
function recur(containingFileName: string | undefined, ref: Reference): void {
|
||||
// An absolute file name for use with TS resolution, e.g. '/DefinitelyTyped/types/foo/index.d.ts'
|
||||
const resolvedFileName = resolveReference(containingFileName, ref);
|
||||
|
||||
if (!resolvedFileName) {
|
||||
return;
|
||||
}
|
||||
seenReferences.add(resolvedFilename);
|
||||
|
||||
if (seenReferences.has(resolvedFileName)) {
|
||||
return;
|
||||
}
|
||||
seenReferences.add(resolvedFileName);
|
||||
|
||||
// E.g. 'index.d.ts' - suitable for lookups in `fs` and for our result
|
||||
const relativeFileName = path.relative(baseDirectory, resolvedFileName);
|
||||
if (path.relative(packageDirectory, resolvedFileName).startsWith("..")) {
|
||||
throw new Error(
|
||||
`${containingFileName ?? "tsconfig.json"}: ` +
|
||||
'Definitions must use global references to other packages, not parent ("../xxx") references.' +
|
||||
`(Based on reference '${ref.text}')`
|
||||
);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:non-literal-fs-path -- Not a reference to the fs package
|
||||
if (fs.exists(resolvedFilename)) {
|
||||
const src = createSourceFile(resolvedFilename, readFileAndThrowOnBOM(resolvedFilename, fs));
|
||||
if (fs.exists(relativeFileName)) {
|
||||
const src = createSourceFile(resolvedFileName, readFileAndThrowOnBOM(relativeFileName, fs), moduleResolutionHost, compilerOptions);
|
||||
if (
|
||||
resolvedFilename.endsWith(".d.ts") ||
|
||||
resolvedFilename.endsWith(".d.mts") ||
|
||||
resolvedFilename.endsWith(".d.cts")
|
||||
relativeFileName.endsWith(".d.ts") ||
|
||||
relativeFileName.endsWith(".d.mts") ||
|
||||
relativeFileName.endsWith(".d.cts")
|
||||
) {
|
||||
types.set(resolvedFilename, src);
|
||||
types.set(relativeFileName, src);
|
||||
} else {
|
||||
tests.set(resolvedFilename, src);
|
||||
tests.set(relativeFileName, src);
|
||||
}
|
||||
|
||||
const { refs, hasNonRelativeImports: result } = findReferencedFiles(
|
||||
src,
|
||||
packageName,
|
||||
path.dirname(resolvedFilename),
|
||||
normalizeSlashes(path.relative(baseDirectory, fs.debugPath()))
|
||||
);
|
||||
refs.forEach(recur);
|
||||
const { refs, hasNonRelativeImports: result } = findReferencedFiles(src, packageName);
|
||||
refs.forEach(ref => recur(resolvedFileName, ref));
|
||||
hasNonRelativeImports = hasNonRelativeImports || result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveModule(importSpecifier: string, fs: FS): string {
|
||||
importSpecifier = importSpecifier.endsWith("/")
|
||||
? importSpecifier.slice(0, importSpecifier.length - 1)
|
||||
: importSpecifier;
|
||||
if (importSpecifier !== "." && importSpecifier !== "..") {
|
||||
// tslint:disable-next-line:non-literal-fs-path -- Not a reference to the fs package
|
||||
if (fs.exists(importSpecifier + ".d.ts")) {
|
||||
return importSpecifier + ".d.ts";
|
||||
}
|
||||
// tslint:disable-next-line:non-literal-fs-path -- Not a reference to the fs package
|
||||
if (fs.exists(importSpecifier + ".ts")) {
|
||||
return importSpecifier + ".ts";
|
||||
}
|
||||
// tslint:disable-next-line:non-literal-fs-path -- Not a reference to the fs package
|
||||
if (fs.exists(importSpecifier + ".tsx")) {
|
||||
return importSpecifier + ".tsx";
|
||||
function resolveReference(containingFileName: string | undefined, { kind, text, resolutionMode }: Reference): string | undefined {
|
||||
switch (kind) {
|
||||
case "path":
|
||||
if (containingFileName) {
|
||||
return ts.resolveTripleslashReference(text, containingFileName);
|
||||
} else {
|
||||
return path.resolve(baseDirectory, text);
|
||||
}
|
||||
case "types":
|
||||
return ts.resolveTypeReferenceDirective(
|
||||
text,
|
||||
assertDefined(containingFileName, "Must have a containing file to resolve a type reference directive"),
|
||||
compilerOptions,
|
||||
moduleResolutionHost,
|
||||
/*redirectedReference*/ undefined,
|
||||
/*cache*/ undefined,
|
||||
resolutionMode).resolvedTypeReferenceDirective?.resolvedFileName;
|
||||
case "import":
|
||||
return ts.resolveModuleName(
|
||||
text,
|
||||
assertDefined(containingFileName, "Must have an containing file to resolve an import"),
|
||||
compilerOptions,
|
||||
moduleResolutionHost,
|
||||
/*cache*/ undefined,
|
||||
/*redirectedReference*/ undefined,
|
||||
resolutionMode
|
||||
).resolvedModule?.resolvedFileName;
|
||||
}
|
||||
}
|
||||
return importSpecifier === "." ? "index.d.ts" : joinPaths(importSpecifier, "index.d.ts");
|
||||
}
|
||||
|
||||
interface Reference {
|
||||
/** <reference path> includes exact filename, so true. import "foo" may reference "foo.d.ts" or "foo/index.d.ts", so false. */
|
||||
readonly exact: boolean;
|
||||
kind: "path" | "import" | "types";
|
||||
text: string;
|
||||
resolutionMode?: ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,76 +228,40 @@ interface Reference {
|
||||
* For example, `baseDirectory` may be `react-router` and `subDirectory` may be `react-router/lib`.
|
||||
* versionsBaseDirectory may be "" when not in typesVersions or ".." when inside `react-router/ts3.1`
|
||||
*/
|
||||
function findReferencedFiles(src: ts.SourceFile, packageName: string, subDirectory: string, baseDirectory: string) {
|
||||
function findReferencedFiles(src: ts.SourceFile, packageName: string) {
|
||||
const refs: Reference[] = [];
|
||||
let hasNonRelativeImports = false;
|
||||
|
||||
for (const ref of src.referencedFiles) {
|
||||
// Any <reference path="foo"> is assumed to be local
|
||||
addReference({ text: ref.fileName, exact: true });
|
||||
refs.push({
|
||||
text: ref.fileName,
|
||||
kind: "path",
|
||||
resolutionMode: ref.resolutionMode,
|
||||
});
|
||||
}
|
||||
for (const ref of src.typeReferenceDirectives) {
|
||||
// only <reference types="../packagename/x" /> references are local (or "packagename/x", though in 3.7 that doesn't work in DT).
|
||||
if (ref.fileName.startsWith("../" + packageName + "/")) {
|
||||
addReference({ text: ref.fileName, exact: false });
|
||||
} else if (ref.fileName.startsWith(packageName + "/")) {
|
||||
addReference({ text: convertToRelativeReference(ref.fileName), exact: false });
|
||||
if (ref.fileName.startsWith("../" + packageName + "/") || ref.fileName.startsWith(packageName + "/")) {
|
||||
refs.push({ kind: "types", text: ref.fileName, resolutionMode: ref.resolutionMode });
|
||||
}
|
||||
}
|
||||
|
||||
for (const ref of imports(src)) {
|
||||
if (ref.startsWith(".")) {
|
||||
addReference({ text: ref, exact: false });
|
||||
} else if (getMangledNameForScopedPackage(ref).startsWith(packageName + "/")) {
|
||||
addReference({ text: convertToRelativeReference(ref), exact: false });
|
||||
hasNonRelativeImports = true;
|
||||
const resolutionMode = ts.getModeForUsageLocation(src, ref);
|
||||
if (ref.text.startsWith(".") || getMangledNameForScopedPackage(ref.text).startsWith(packageName + "/")) {
|
||||
refs.push({ kind: "import", text: ref.text, resolutionMode });
|
||||
hasNonRelativeImports = !ref.text.startsWith(".");
|
||||
}
|
||||
}
|
||||
return { refs, hasNonRelativeImports };
|
||||
|
||||
function addReference(ref: Reference): void {
|
||||
// `path.normalize` may add windows slashes
|
||||
let full = normalizeSlashes(
|
||||
path.normalize(joinPaths(subDirectory, assertNoWindowsSlashes(src.fileName, ref.text)))
|
||||
);
|
||||
// allow files in typesVersions directories (i.e. 'ts3.1') to reference files in parent directory
|
||||
if (full.startsWith("../" + packageName + "/")) {
|
||||
full = full.slice(packageName.length + 4);
|
||||
} else if (baseDirectory && full.startsWith("../" + baseDirectory + "/")) {
|
||||
full = full.slice(baseDirectory.length + 4);
|
||||
} else if (
|
||||
full.startsWith("..") &&
|
||||
(baseDirectory === "" || path.normalize(joinPaths(baseDirectory, full)).startsWith(".."))
|
||||
) {
|
||||
throw new Error(
|
||||
`${src.fileName}: ` +
|
||||
'Definitions must use global references to other packages, not parent ("../xxx") references.' +
|
||||
`(Based on reference '${ref.text}')`
|
||||
);
|
||||
}
|
||||
ref.text = full;
|
||||
refs.push(ref);
|
||||
}
|
||||
|
||||
/** boring/foo -> ./foo when subDirectory === '.'; ../foo when it's === 'x'; ../../foo when it's 'x/y' */
|
||||
function convertToRelativeReference(name: string) {
|
||||
let relative = ".";
|
||||
if (subDirectory !== ".") {
|
||||
relative += "/..".repeat(subDirectory.split("/").length);
|
||||
if (baseDirectory && subDirectory.startsWith("..")) {
|
||||
relative = relative.slice(0, -2) + baseDirectory;
|
||||
}
|
||||
}
|
||||
return relative + name.slice(packageName.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All strings referenced in `import` statements.
|
||||
* Does *not* include <reference> directives.
|
||||
*/
|
||||
function imports({ statements }: ts.SourceFile): Iterable<string> {
|
||||
const result: string[] = [];
|
||||
function imports({ statements }: ts.SourceFile): Iterable<ts.StringLiteralLike> {
|
||||
const result: ts.StringLiteralLike[] = [];
|
||||
for (const node of statements) {
|
||||
recur(node);
|
||||
}
|
||||
@@ -284,7 +273,7 @@ function imports({ statements }: ts.SourceFile): Iterable<string> {
|
||||
case ts.SyntaxKind.ExportDeclaration: {
|
||||
const { moduleSpecifier } = node as ts.ImportDeclaration | ts.ExportDeclaration;
|
||||
if (moduleSpecifier && moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) {
|
||||
result.push((moduleSpecifier as ts.StringLiteral).text);
|
||||
result.push((moduleSpecifier as ts.StringLiteral));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -300,7 +289,7 @@ function imports({ statements }: ts.SourceFile): Iterable<string> {
|
||||
case ts.SyntaxKind.ImportType: {
|
||||
const { argument } = node as ts.ImportTypeNode;
|
||||
if (ts.isLiteralTypeNode(argument) && ts.isStringLiteral(argument.literal)) {
|
||||
result.push(argument.literal.text);
|
||||
result.push(argument.literal);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -311,12 +300,12 @@ function imports({ statements }: ts.SourceFile): Iterable<string> {
|
||||
}
|
||||
}
|
||||
|
||||
function parseRequire(reference: ts.ExternalModuleReference): string {
|
||||
function parseRequire(reference: ts.ExternalModuleReference): ts.StringLiteralLike {
|
||||
const { expression } = reference;
|
||||
if (!expression || !ts.isStringLiteral(expression)) {
|
||||
throw new Error(`Bad 'import =' reference: ${reference.getText()}`);
|
||||
}
|
||||
return expression.text;
|
||||
return expression;
|
||||
}
|
||||
|
||||
function isValueNamespace(ns: ts.ModuleDeclaration): boolean {
|
||||
@@ -360,12 +349,14 @@ export function getTestDependencies(
|
||||
packageName: string,
|
||||
testFiles: Iterable<string>,
|
||||
dependencies: ReadonlySet<string>,
|
||||
fs: FS
|
||||
fs: FS,
|
||||
moduleResolutionHost: ts.ModuleResolutionHost,
|
||||
compilerOptions: ts.CompilerOptions,
|
||||
): Iterable<string> {
|
||||
const testDependencies = new Set<string>();
|
||||
for (const filename of testFiles) {
|
||||
const content = readFileAndThrowOnBOM(filename, fs);
|
||||
const sourceFile = createSourceFile(filename, content);
|
||||
const sourceFile = createSourceFile(filename, content, moduleResolutionHost, compilerOptions);
|
||||
const { fileName, referencedFiles, typeReferenceDirectives } = sourceFile;
|
||||
const filePath = () => path.join(packageName, fileName);
|
||||
let hasImports = false;
|
||||
@@ -388,8 +379,8 @@ export function getTestDependencies(
|
||||
}
|
||||
for (const imported of imports(sourceFile)) {
|
||||
hasImports = true;
|
||||
if (!imported.startsWith(".") && !dependencies.has(imported)) {
|
||||
testDependencies.add(imported);
|
||||
if (!imported.text.startsWith(".") && !dependencies.has(imported.text)) {
|
||||
testDependencies.add(imported.text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,6 +404,8 @@ export function getTestDependencies(
|
||||
return testDependencies;
|
||||
}
|
||||
|
||||
export function createSourceFile(filename: string, content: string): ts.SourceFile {
|
||||
return ts.createSourceFile(filename, content, ts.ScriptTarget.Latest, /*setParentNodes*/ false);
|
||||
export function createSourceFile(filename: string, content: string, moduleResolutionHost: ts.ModuleResolutionHost, compilerOptions: ts.CompilerOptions): ts.SourceFile {
|
||||
const file = ts.createSourceFile(filename, content, ts.ScriptTarget.Latest, /*setParentNodes*/ false);
|
||||
file.impliedNodeFormat = ts.getImpliedNodeFormatForFile(filename as ts.Path, /*packageJsonInfoCache*/ undefined, moduleResolutionHost, compilerOptions);
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { parseHeaderOrFail } from "@definitelytyped/header-parser";
|
||||
import { Dir, FS, InMemoryFS, mangleScopedPackage } from "@definitelytyped/utils";
|
||||
import * as semver from "semver";
|
||||
|
||||
class DTMock {
|
||||
export class DTMock {
|
||||
public readonly fs: FS;
|
||||
private readonly root: Dir;
|
||||
|
||||
@@ -19,7 +19,7 @@ class DTMock {
|
||||
},
|
||||
})
|
||||
);
|
||||
this.fs = new InMemoryFS(this.root, "DefinitelyTyped");
|
||||
this.fs = new InMemoryFS(this.root, "/DefinitelyTyped/");
|
||||
}
|
||||
|
||||
public pkgDir(packageName: string): Dir {
|
||||
@@ -172,12 +172,7 @@ untested.d.ts
|
||||
declare var x: number
|
||||
`
|
||||
);
|
||||
globby.set(
|
||||
"merges.d.ts",
|
||||
`
|
||||
declare var y: number
|
||||
`
|
||||
);
|
||||
|
||||
globby.set(
|
||||
"sneaky.d.ts",
|
||||
`
|
||||
@@ -194,7 +189,6 @@ var z = x;
|
||||
tests.set(
|
||||
"other-tests.ts",
|
||||
`
|
||||
/// <reference types="globby/merges" />
|
||||
var z = y;
|
||||
`
|
||||
);
|
||||
|
||||
@@ -40,7 +40,7 @@ export async function parseDefinitions(
|
||||
} else {
|
||||
log.info("Parsing in main process...");
|
||||
for (const packageName of packageNames) {
|
||||
typings[packageName] = await getTypingInfo(packageName, typesFS.subDir(packageName));
|
||||
typings[packageName] = await getTypingInfo(packageName, dt);
|
||||
}
|
||||
}
|
||||
log.info("Parsing took " + (Date.now() - start) / 1000 + " s");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import path from "path";
|
||||
import { DiskFS } from "@definitelytyped/utils";
|
||||
import { createMockDT } from "../src/mocks";
|
||||
import { getTypingInfo } from "../src/lib/definition-parser";
|
||||
@@ -7,14 +8,14 @@ describe(getTypingInfo, () => {
|
||||
const dt = createMockDT();
|
||||
dt.addOldVersionOfPackage("jquery", "1.42");
|
||||
dt.addOldVersionOfPackage("jquery", "2");
|
||||
const info = await getTypingInfo("jquery", dt.pkgFS("jquery"));
|
||||
const info = await getTypingInfo("jquery", dt.fs);
|
||||
|
||||
expect(Object.keys(info).sort()).toEqual(["1.42", "2.0", "3.3"]);
|
||||
});
|
||||
|
||||
it("works for a package with dependencies", async () => {
|
||||
const dt = createMockDT();
|
||||
const info = await getTypingInfo("has-dependency", dt.pkgFS("has-dependency"));
|
||||
const info = await getTypingInfo("has-dependency", dt.fs);
|
||||
expect(info).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -65,7 +66,7 @@ export function myFunction(arg:string): string;
|
||||
|
||||
dt.addOldVersionOfPackage("@ckeditor/ckeditor5-utils", "10");
|
||||
|
||||
const info = await getTypingInfo("@ckeditor/ckeditor5-engine", dt.pkgFS("ckeditor__ckeditor5-engine"));
|
||||
const info = await getTypingInfo("@ckeditor/ckeditor5-engine", dt.fs);
|
||||
expect(info).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -124,7 +125,7 @@ export * from 'buffer';
|
||||
} `
|
||||
);
|
||||
|
||||
const info = await getTypingInfo("safer", dt.pkgFS("safer"));
|
||||
const info = await getTypingInfo("safer", dt.fs);
|
||||
expect(info).toBeDefined();
|
||||
expect(info["1.0"].dependencies).toEqual({ node: "*" });
|
||||
});
|
||||
@@ -189,7 +190,7 @@ const a = new webpack.AutomaticPrefetchPlugin();
|
||||
}`
|
||||
);
|
||||
|
||||
const info = await getTypingInfo("webpack", dt.pkgFS("webpack"));
|
||||
const info = await getTypingInfo("webpack", dt.fs);
|
||||
expect(info).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -198,7 +199,7 @@ const a = new webpack.AutomaticPrefetchPlugin();
|
||||
getTypingInfo(
|
||||
"typeref-fails",
|
||||
new DiskFS(
|
||||
"packages/definitions-parser/test/fixtures/rejects-references-to-old-versions-of-other-types-packages/"
|
||||
path.resolve(__dirname, "fixtures/rejects-references-to-old-versions-of-other-types-packages/")
|
||||
)
|
||||
)
|
||||
).rejects.toThrow("do not directly import specific versions of another types package");
|
||||
@@ -207,7 +208,7 @@ const a = new webpack.AutomaticPrefetchPlugin();
|
||||
it("allows references to old versions of self", async () => {
|
||||
const info = await getTypingInfo(
|
||||
"fail",
|
||||
new DiskFS("packages/definitions-parser/test/fixtures/allows-references-to-old-versions-of-self/")
|
||||
new DiskFS(path.resolve(__dirname, "fixtures/allows-references-to-old-versions-of-self/"))
|
||||
);
|
||||
expect(info).toBeDefined();
|
||||
});
|
||||
@@ -261,7 +262,7 @@ import route = require('@ember/routing/route');
|
||||
}`
|
||||
);
|
||||
|
||||
const info = await getTypingInfo("ember", dt.pkgFS("ember"));
|
||||
const info = await getTypingInfo("ember", dt.fs);
|
||||
expect(info["2.8"].testDependencies).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -269,18 +270,27 @@ import route = require('@ember/routing/route');
|
||||
const info = await getTypingInfo(
|
||||
"styled-components-react-native",
|
||||
new DiskFS(
|
||||
"packages/definitions-parser/test/fixtures/doesnt-omit-dependencies-if-only-some-deep-modules-are-declared/"
|
||||
path.resolve(__dirname, "fixtures/doesnt-omit-dependencies-if-only-some-deep-modules-are-declared/")
|
||||
)
|
||||
);
|
||||
expect(info["5.1"].dependencies).toEqual({ "styled-components": "*" });
|
||||
});
|
||||
|
||||
it("rejects relative references to other packages", async () => {
|
||||
expect(() => getTypingInfo(
|
||||
"referencing",
|
||||
new DiskFS(
|
||||
path.resolve(__dirname, "fixtures/rejects-relative-references-to-other-packages/")
|
||||
)
|
||||
)).rejects.toThrow("Definitions must use global references to other packages");
|
||||
});
|
||||
|
||||
describe("concerning multiple versions", () => {
|
||||
it("records what the version directory looks like on disk", async () => {
|
||||
const dt = createMockDT();
|
||||
dt.addOldVersionOfPackage("jquery", "2");
|
||||
dt.addOldVersionOfPackage("jquery", "1.5");
|
||||
const info = await getTypingInfo("jquery", dt.pkgFS("jquery"));
|
||||
const info = await getTypingInfo("jquery", dt.fs);
|
||||
|
||||
expect(info).toEqual({
|
||||
"1.5": expect.objectContaining({
|
||||
@@ -300,7 +310,7 @@ import route = require('@ember/routing/route');
|
||||
const dt = createMockDT();
|
||||
dt.addOldVersionOfPackage("jquery", "2");
|
||||
dt.addOldVersionOfPackage("jquery", "1.5");
|
||||
const info = await getTypingInfo("jquery", dt.pkgFS("jquery"));
|
||||
const info = await getTypingInfo("jquery", dt.fs);
|
||||
|
||||
expect(info).toEqual({
|
||||
"1.5": expect.objectContaining({
|
||||
@@ -343,7 +353,7 @@ import route = require('@ember/routing/route');
|
||||
|
||||
dt.addOldVersionOfPackage("@ckeditor/ckeditor5-utils", "10");
|
||||
|
||||
const info = await getTypingInfo("@ckeditor/ckeditor5-utils", dt.pkgFS("ckeditor__ckeditor5-utils"));
|
||||
const info = await getTypingInfo("@ckeditor/ckeditor5-utils", dt.fs);
|
||||
expect(info).toEqual({
|
||||
"10.0": expect.objectContaining({
|
||||
pathMappings: {
|
||||
@@ -362,7 +372,7 @@ import route = require('@ember/routing/route');
|
||||
const dt = createMockDT();
|
||||
dt.addOldVersionOfPackage("jquery", "3");
|
||||
|
||||
return expect(getTypingInfo("jquery", dt.pkgFS("jquery"))).rejects.toThrow(
|
||||
return expect(getTypingInfo("jquery", dt.fs)).rejects.toThrow(
|
||||
"The latest version of the 'jquery' package is 3.3, so the subdirectory 'v3' is not allowed; " +
|
||||
"since it applies to any 3.* version, up to and including 3.3."
|
||||
);
|
||||
@@ -372,7 +382,7 @@ import route = require('@ember/routing/route');
|
||||
const dt = createMockDT();
|
||||
dt.addOldVersionOfPackage("jquery", "3.3");
|
||||
|
||||
return expect(getTypingInfo("jquery", dt.pkgFS("jquery"))).rejects.toThrow(
|
||||
return expect(getTypingInfo("jquery", dt.fs)).rejects.toThrow(
|
||||
"The latest version of the 'jquery' package is 3.3, so the subdirectory 'v3.3' is not allowed."
|
||||
);
|
||||
});
|
||||
@@ -381,7 +391,7 @@ import route = require('@ember/routing/route');
|
||||
const dt = createMockDT();
|
||||
dt.addOldVersionOfPackage("jquery", "3.0");
|
||||
|
||||
return expect(getTypingInfo("jquery", dt.pkgFS("jquery"))).resolves.toBeDefined();
|
||||
return expect(getTypingInfo("jquery", dt.fs)).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it("checks that older versions with non-relative imports have wildcard path mappings", () => {
|
||||
@@ -393,7 +403,7 @@ import route = require('@ember/routing/route');
|
||||
`
|
||||
);
|
||||
dt.addOldVersionOfPackage("jquery", "1");
|
||||
return expect(getTypingInfo("jquery", dt.pkgFS("jquery"))).rejects.toThrow(
|
||||
return expect(getTypingInfo("jquery", dt.fs)).rejects.toThrow(
|
||||
'jquery: Older version 1 must have a "paths" entry of "jquery/*": ["jquery/v1/*"]'
|
||||
);
|
||||
});
|
||||
@@ -423,7 +433,7 @@ import first from "@ckeditor/ckeditor5-utils/src/first";
|
||||
dt.addOldVersionOfPackage("@ckeditor/ckeditor5-utils", "10");
|
||||
|
||||
return expect(
|
||||
getTypingInfo("ckeditor__ckeditor5-utils", dt.pkgFS("ckeditor__ckeditor5-utils"))
|
||||
getTypingInfo("ckeditor__ckeditor5-utils", dt.fs)
|
||||
).rejects.toThrow(
|
||||
'@ckeditor/ckeditor5-utils: Older version 10 must have a "paths" entry of "@ckeditor/ckeditor5-utils/*": ["ckeditor__ckeditor5-utils/v10/*"]'
|
||||
);
|
||||
@@ -433,6 +443,6 @@ import first from "@ckeditor/ckeditor5-utils/src/first";
|
||||
|
||||
it("allows wildcard scope path mappings", () => {
|
||||
const dt = createMockDT();
|
||||
return expect(getTypingInfo("wordpress__plugins", dt.pkgFS("wordpress__plugins"))).resolves.toBeDefined();
|
||||
return expect(getTypingInfo("wordpress__plugins", dt.fs)).resolves.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Type definitions for referenced 1.0
|
||||
// Project: https://youtube.com/s-fails
|
||||
// Definitions by: Type Ref Fails <https://github.com/typeref-fails>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
export interface Referenced {}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"lib": ["es6"],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"baseUrl": "../",
|
||||
"typeRoots": ["../"],
|
||||
"types": [],
|
||||
"noEmit": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"files": ["index.d.ts"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Type definitions for referencing 1.0
|
||||
// Project: https://youtube.com/s-fails
|
||||
// Definitions by: Type Ref Fails <https://github.com/typeref-fails>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
import { Referenced } from "../referenced";
|
||||
|
||||
export interface Referencing extends Referenced {}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"lib": ["es6"],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"baseUrl": "../",
|
||||
"typeRoots": ["../"],
|
||||
"types": [],
|
||||
"noEmit": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"files": ["index.d.ts"]
|
||||
}
|
||||
@@ -26,7 +26,7 @@ testo({
|
||||
root.set("file1.txt", "ok");
|
||||
const dir = root.subdir("sub1");
|
||||
dir.set("file2.txt", "x");
|
||||
const fs: FS = new InMemoryFS(root, "test/");
|
||||
const fs: FS = new InMemoryFS(root, "/test/");
|
||||
expect(fs.exists("file1.txt")).toBe(true);
|
||||
expect(fs.readFile("file1.txt")).toBe("ok");
|
||||
expect(fs.readFile("sub1/file2.txt")).toBe("x");
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import * as ts from "typescript";
|
||||
import { Dir, InMemoryFS } from "@definitelytyped/utils";
|
||||
import { createMockDT } from "../src/mocks";
|
||||
import { createModuleResolutionHost } from "@definitelytyped/utils";
|
||||
import { DTMock, createMockDT } from "../src/mocks";
|
||||
import { testo } from "./utils";
|
||||
import { allReferencedFiles, getModuleInfo, getTestDependencies } from "../src/lib/module-info";
|
||||
|
||||
const fs = createMockDT().fs;
|
||||
const moduleResolutionHost = createModuleResolutionHost(fs);
|
||||
const compilerOptions = { module: ts.ModuleKind.CommonJS, baseUrl: "/DefinitelyTyped/types", typeRoots: ["/DefinitelyTyped/types"] };
|
||||
|
||||
function getBoringReferences() {
|
||||
return allReferencedFiles(
|
||||
["index.d.ts", "boring-tests.ts"],
|
||||
fs.subDir("types").subDir("boring"),
|
||||
"boring",
|
||||
"types/boring"
|
||||
moduleResolutionHost,
|
||||
compilerOptions
|
||||
);
|
||||
}
|
||||
testo({
|
||||
@@ -31,7 +35,8 @@ testo({
|
||||
["boring-tests.ts"],
|
||||
fs.subDir("types").subDir("boring"),
|
||||
"boring",
|
||||
"types/boring"
|
||||
moduleResolutionHost,
|
||||
compilerOptions
|
||||
);
|
||||
expect(Array.from(types.keys())).toEqual([
|
||||
"secondary.d.ts",
|
||||
@@ -47,7 +52,8 @@ testo({
|
||||
["jquery-tests.ts", "index.d.ts"],
|
||||
fs.subDir("types").subDir("jquery"),
|
||||
"jquery",
|
||||
"types/jquery"
|
||||
moduleResolutionHost,
|
||||
compilerOptions
|
||||
);
|
||||
expect(Array.from(types.keys())).toEqual(["index.d.ts", "JQuery.d.ts"]);
|
||||
expect(Array.from(tests.keys())).toEqual(["jquery-tests.ts"]);
|
||||
@@ -57,21 +63,22 @@ testo({
|
||||
["globby-tests.ts", "test/other-tests.ts"],
|
||||
fs.subDir("types").subDir("globby"),
|
||||
"globby",
|
||||
"types/globby"
|
||||
moduleResolutionHost,
|
||||
compilerOptions
|
||||
);
|
||||
expect(Array.from(types.keys())).toEqual(["merges.d.ts"]);
|
||||
expect(Array.from(types.keys())).toEqual([]);
|
||||
expect(Array.from(tests.keys())).toEqual(["globby-tests.ts", "test/other-tests.ts"]);
|
||||
},
|
||||
allReferencedFilesIncludesTypesImports() {
|
||||
const pkg = new Dir(undefined);
|
||||
const dtMock = new DTMock();
|
||||
const pkg = dtMock.pkgDir("mock");
|
||||
pkg.set(
|
||||
"index.d.ts",
|
||||
`type T = import("./types");
|
||||
`
|
||||
);
|
||||
pkg.set("types.d.ts", "");
|
||||
const memFS = new InMemoryFS(pkg, "types/mock");
|
||||
const { types, tests } = allReferencedFiles(["index.d.ts"], memFS, "mock", "types/mock");
|
||||
const { types, tests } = allReferencedFiles(["index.d.ts"], dtMock.fs.subDir("types/mock"), "mock", createModuleResolutionHost(dtMock.fs), compilerOptions);
|
||||
expect(Array.from(types.keys())).toEqual(["index.d.ts", "types.d.ts"]);
|
||||
expect(Array.from(tests.keys())).toEqual([]);
|
||||
},
|
||||
@@ -97,32 +104,35 @@ testo({
|
||||
["index.d.ts", "globby-tests.ts", "test/other-tests.ts"],
|
||||
fs.subDir("types").subDir("globby"),
|
||||
"globby",
|
||||
"types/globby"
|
||||
moduleResolutionHost,
|
||||
compilerOptions
|
||||
);
|
||||
expect(Array.from(types.keys())).toEqual(["index.d.ts", "sneaky.d.ts", "merges.d.ts"]);
|
||||
expect(Array.from(types.keys())).toEqual(["index.d.ts", "sneaky.d.ts"]);
|
||||
const i = getModuleInfo("globby", types);
|
||||
expect(i.dependencies).toEqual(new Set(["andere/snee"]));
|
||||
},
|
||||
selfInScopedPackage() {
|
||||
const scoped = new Dir(undefined);
|
||||
const dtMock = new DTMock();
|
||||
const scoped = dtMock.pkgDir("rdfjs__to-ntriples");
|
||||
scoped.set(
|
||||
"index.d.ts",
|
||||
`import "@rdfjs/to-ntriples/component";
|
||||
`
|
||||
);
|
||||
scoped.set("component.d.ts", "");
|
||||
const memFS = new InMemoryFS(scoped, "types/rdfjs__to-ntriples");
|
||||
const { types, tests } = allReferencedFiles(
|
||||
["index.d.ts"],
|
||||
memFS,
|
||||
dtMock.fs.subDir("types/rdfjs__to-ntriples"),
|
||||
"rdfjs__to-ntriples",
|
||||
"types/rdfjs__to-ntriples"
|
||||
createModuleResolutionHost(dtMock.fs),
|
||||
{ ...compilerOptions, paths: { "@rdfjs/to-ntriples/*": ["rdfjs__to-ntriples/*"] } }
|
||||
);
|
||||
expect(Array.from(types.keys())).toEqual(["index.d.ts", "component.d.ts"]);
|
||||
expect(Array.from(tests.keys())).toEqual([]);
|
||||
},
|
||||
selfInTypesVersionsParent() {
|
||||
const pkg = new Dir(undefined);
|
||||
const dtMock = new DTMock();
|
||||
const pkg = dtMock.pkgDir("mock");
|
||||
const ts20 = pkg.subdir("ts2.0");
|
||||
ts20.set(
|
||||
"index.d.ts",
|
||||
@@ -136,15 +146,20 @@ testo({
|
||||
`import "mock/component";
|
||||
`
|
||||
);
|
||||
const memFS = new InMemoryFS(ts20, "types/mock/ts2.0");
|
||||
const { types, tests } = allReferencedFiles(["index.d.ts"], memFS, "mock", "types/mock");
|
||||
|
||||
const { types, tests } = allReferencedFiles(
|
||||
["index.d.ts"],
|
||||
dtMock.fs.subDir("types/mock/ts2.0"),
|
||||
"mock",
|
||||
createModuleResolutionHost(dtMock.fs),
|
||||
{ ...compilerOptions, paths: { "mock/*": ["mock/ts2.0/*"] } });
|
||||
expect(Array.from(types.keys())).toEqual(["index.d.ts", "../ts1.0/index.d.ts", "component.d.ts"]);
|
||||
expect(Array.from(tests.keys())).toEqual([]);
|
||||
},
|
||||
getTestDependenciesWorks() {
|
||||
const { types, tests } = getBoringReferences();
|
||||
const i = getModuleInfo("boring", types);
|
||||
const d = getTestDependencies("boring", tests.keys(), i.dependencies, fs.subDir("types").subDir("boring"));
|
||||
const d = getTestDependencies("boring", tests.keys(), i.dependencies, fs.subDir("types").subDir("boring"), moduleResolutionHost, compilerOptions);
|
||||
expect(d).toEqual(new Set(["boring", "boring/commonjs", "boring/secondary", "boring/v1", "super-big-fun-hus"]));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ describe(TypingsVersions, () => {
|
||||
dt.addOldVersionOfPackage("jquery", "1");
|
||||
dt.addOldVersionOfPackage("jquery", "2");
|
||||
dt.addOldVersionOfPackage("jquery", "2.5");
|
||||
versions = new TypingsVersions(await getTypingInfo("jquery", dt.pkgFS("jquery")));
|
||||
versions = new TypingsVersions(await getTypingInfo("jquery", dt.fs));
|
||||
});
|
||||
|
||||
it("sorts the data from latest to oldest version", () => {
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
"rootDir": "src",
|
||||
"types": ["node"],
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface TesterOptions extends ParseDefinitionsOptions {
|
||||
export const defaultLocalOptions: TesterOptions = {
|
||||
definitelyTypedPath: "../../../DefinitelyTyped",
|
||||
progress: true,
|
||||
parseInParallel: true,
|
||||
parseInParallel: !process.env.VSCODE_INSPECTOR_OPTIONS,
|
||||
};
|
||||
|
||||
export const defaultRemoteOptions: ParseDefinitionsOptions = {
|
||||
|
||||
@@ -39,7 +39,7 @@ if (!module.parent) {
|
||||
}
|
||||
|
||||
async function single(singleName: string, dt: FS): Promise<void> {
|
||||
const data = await getTypingInfo(singleName, dt.subDir("types").subDir(singleName));
|
||||
const data = await getTypingInfo(singleName, dt);
|
||||
const typings = { [singleName]: data };
|
||||
await writeDataFile(typesDataFilename, typings);
|
||||
console.log(JSON.stringify(data, undefined, 4));
|
||||
|
||||
@@ -60,7 +60,7 @@ function defaultFS(): FS {
|
||||
`
|
||||
);
|
||||
pkg.set("jquery.test.ts", "// tests");
|
||||
const memFS = new InMemoryFS(pkg, "types/mock");
|
||||
const memFS = new InMemoryFS(pkg, "/types/mock/");
|
||||
return memFS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import assert from "assert";
|
||||
import { relative, resolve } from "path";
|
||||
import { assertDefined } from "./assertions";
|
||||
import { pathExistsSync, readdirSync, statSync } from "fs-extra";
|
||||
import { readFileSync, readJsonSync } from "./io";
|
||||
@@ -37,6 +38,18 @@ export interface FS {
|
||||
debugPath(): string;
|
||||
}
|
||||
|
||||
export function createModuleResolutionHost(fs: FS): import("typescript").ModuleResolutionHost {
|
||||
return {
|
||||
fileExists: (filename) => fs.exists(filename),
|
||||
readFile: (filename) => fs.readFile(filename),
|
||||
directoryExists: (directoryName) => fs.exists(directoryName),
|
||||
getCurrentDirectory: () => "",
|
||||
realpath: (path) => path,
|
||||
useCaseSensitiveFileNames: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
interface ReadonlyDir extends ReadonlyMap<string, ReadonlyDir | string> {
|
||||
readonly parent: Dir | undefined;
|
||||
}
|
||||
@@ -70,15 +83,28 @@ export class Dir extends Map<string, Dir | string> implements ReadonlyDir {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureTrailingSlash(dir: string) {
|
||||
return dir.endsWith("/") ? dir : dir + "/";
|
||||
}
|
||||
|
||||
export class InMemoryFS implements FS {
|
||||
/** pathToRoot is just for debugging */
|
||||
constructor(readonly curDir: ReadonlyDir, readonly pathToRoot: string) {}
|
||||
constructor(readonly curDir: ReadonlyDir, readonly rootPrefix: string) {
|
||||
this.rootPrefix = ensureTrailingSlash(rootPrefix);
|
||||
assert(rootPrefix[0] === "/", `rootPrefix must be absolute: ${rootPrefix}`);
|
||||
}
|
||||
|
||||
private tryGetEntry(path: string): ReadonlyDir | string | undefined {
|
||||
validatePath(path);
|
||||
if (path[0] === "/") {
|
||||
path = relative(this.rootPrefix, path);
|
||||
}
|
||||
if (path === "") {
|
||||
return this.curDir;
|
||||
}
|
||||
const needsDir = path.endsWith("/");
|
||||
if (needsDir) {
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
|
||||
const components = path.split("/");
|
||||
const baseName = assertDefined(components.pop());
|
||||
let dir = this.curDir;
|
||||
@@ -89,18 +115,19 @@ export class InMemoryFS implements FS {
|
||||
}
|
||||
if (!(entry instanceof Dir)) {
|
||||
throw new Error(
|
||||
`No file system entry at ${this.pathToRoot}/${path}. Siblings are: ${Array.from(dir.keys()).toString()}`
|
||||
`No file system entry at ${this.rootPrefix}/${path}. Siblings are: ${Array.from(dir.keys()).toString()}`
|
||||
);
|
||||
}
|
||||
dir = entry;
|
||||
}
|
||||
return dir.get(baseName);
|
||||
const res = dir.get(baseName);
|
||||
return needsDir ? (res instanceof Dir ? res : undefined) : res;
|
||||
}
|
||||
|
||||
private getEntry(path: string): ReadonlyDir | string {
|
||||
const entry = this.tryGetEntry(path);
|
||||
if (entry === undefined) {
|
||||
throw new Error(`No file system entry at ${this.pathToRoot}/${path}`);
|
||||
throw new Error(`No file system entry at ${this.rootPrefix}/${path}`);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
@@ -108,7 +135,7 @@ export class InMemoryFS implements FS {
|
||||
private getDir(dirPath: string): Dir {
|
||||
const res = this.getEntry(dirPath);
|
||||
if (!(res instanceof Dir)) {
|
||||
throw new Error(`${this.pathToRoot}/${dirPath} is a file, not a directory.`);
|
||||
throw new Error(`${this.rootPrefix}/${dirPath} is a file, not a directory.`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -116,7 +143,7 @@ export class InMemoryFS implements FS {
|
||||
readFile(filePath: string): string {
|
||||
const res = this.getEntry(filePath);
|
||||
if (typeof res !== "string") {
|
||||
throw new Error(`${this.pathToRoot}/${filePath} is a directory, not a file.`);
|
||||
throw new Error(`${this.rootPrefix}/${filePath} is a directory, not a file.`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -138,25 +165,23 @@ export class InMemoryFS implements FS {
|
||||
}
|
||||
|
||||
subDir(path: string): FS {
|
||||
return new InMemoryFS(this.getDir(path), joinPaths(this.pathToRoot, path));
|
||||
assert(path[0] !== "/", "Cannot use absolute paths with InMemoryFS.subDir");
|
||||
return new InMemoryFS(this.getDir(path), resolve(this.rootPrefix, path));
|
||||
}
|
||||
|
||||
debugPath(): string {
|
||||
return this.pathToRoot;
|
||||
return this.rootPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
export class DiskFS implements FS {
|
||||
constructor(private readonly rootPrefix: string) {
|
||||
assert(rootPrefix.endsWith("/"));
|
||||
assert(rootPrefix.startsWith("/"), "DiskFS must use absolute paths");
|
||||
this.rootPrefix = ensureTrailingSlash(rootPrefix);
|
||||
}
|
||||
|
||||
private getPath(path: string | undefined): string {
|
||||
if (path === undefined) {
|
||||
return this.rootPrefix;
|
||||
}
|
||||
validatePath(path);
|
||||
return this.rootPrefix + path;
|
||||
return resolve(this.rootPrefix, path ?? "");
|
||||
}
|
||||
|
||||
readdir(dirPath?: string): readonly string[] {
|
||||
@@ -189,16 +214,3 @@ export class DiskFS implements FS {
|
||||
return this.rootPrefix.slice(0, this.rootPrefix.length - 1); // remove trailing '/'
|
||||
}
|
||||
}
|
||||
|
||||
/** FS only handles simple paths like `foo/bar` or `../foo`. No `./foo` or `/foo`. */
|
||||
function validatePath(path: string): void {
|
||||
if (path.startsWith(".") && path !== ".editorconfig" && path !== ".eslintrc.json" && !path.startsWith("../")) {
|
||||
throw new Error(`${path}: filesystem doesn't support paths of the form './x'.`);
|
||||
}
|
||||
if (path.startsWith("/")) {
|
||||
throw new Error(`${path}: filesystem doesn't support paths of the form '/xxx'.`);
|
||||
}
|
||||
if (path.endsWith("/")) {
|
||||
throw new Error(`${path}: filesystem doesn't support paths of the form 'xxx/'.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ export function downloadAndExtractFile(url: string, log: LoggerWithErrors): Prom
|
||||
extract.on("finish", () => {
|
||||
log.info("Done receiving " + url);
|
||||
clearTimeout(timeout);
|
||||
resolve(new InMemoryFS(root.finish(), ""));
|
||||
resolve(new InMemoryFS(root.finish(), "/"));
|
||||
});
|
||||
|
||||
response.pipe(zlib.createGunzip()).pipe(extract);
|
||||
|
||||
Reference in New Issue
Block a user