Skip to content

Commit 9a6bb75

Browse files
authored
Merge pull request #147 from arethetypeswrong/improved-default-export
Improved MissingExportDefault detection
2 parents 0851a27 + 2ef4283 commit 9a6bb75

15 files changed

+1406
-233
lines changed

.changeset/clean-ligers-exist.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@arethetypeswrong/core": minor
3+
---
4+
5+
Improve detection for MissingExportEquals and FalseExportDefault

packages/core/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
"prepack": "pnpm tsc"
2525
},
2626
"type": "module",
27+
"imports": {
28+
"#internal/*": "./dist/internal/*"
29+
},
2730
"exports": {
2831
".": {
2932
"development": "./src/index.ts",

packages/core/src/checkPackage.ts

Lines changed: 3 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
1-
import ts from "typescript";
21
import type { Package } from "./createPackage.js";
32
import checks from "./internal/checks/index.js";
43
import type { AnyCheck, CheckDependenciesContext } from "./internal/defineCheck.js";
5-
import { CompilerHostWrapper, createCompilerHosts, type CompilerHosts } from "./multiCompilerHost.js";
4+
import { createCompilerHosts } from "./internal/multiCompilerHost.js";
65
import type {
76
AnalysisTypes,
8-
BuildTool,
97
CheckResult,
10-
EntrypointInfo,
118
EntrypointResolutionAnalysis,
12-
ModuleKind,
139
Problem,
1410
ProgramInfo,
15-
Resolution,
16-
ResolutionKind,
1711
ResolutionOption,
1812
} from "./types.js";
19-
import { allBuildTools, getResolutionKinds, getResolutionOption, visitResolutions } from "./utils.js";
13+
import { getResolutionOption, visitResolutions } from "./utils.js";
14+
import { getEntrypointInfo, getModuleKinds, getBuildTools } from "./internal/getEntrypointInfo.js";
2015

2116
export interface CheckPackageOptions {
2217
/**
@@ -125,204 +120,3 @@ export async function checkPackage(pkg: Package, options?: CheckPackageOptions):
125120
}
126121
}
127122
}
128-
129-
function getEntrypoints(fs: Package, exportsObject: any, options: CheckPackageOptions | undefined): string[] {
130-
if (options?.entrypoints) {
131-
return options.entrypoints.map((e) => formatEntrypointString(e, fs.packageName));
132-
}
133-
if (exportsObject === undefined && fs) {
134-
const proxies = getProxyDirectories(`/node_modules/${fs.packageName}`, fs);
135-
if (proxies.length === 0) {
136-
return ["."];
137-
}
138-
return proxies;
139-
}
140-
const detectedSubpaths = getSubpaths(exportsObject);
141-
if (detectedSubpaths.length === 0) {
142-
detectedSubpaths.push(".");
143-
}
144-
const included = unique([
145-
...detectedSubpaths,
146-
...(options?.includeEntrypoints?.map((e) => formatEntrypointString(e, fs.packageName)) ?? []),
147-
]);
148-
if (!options?.excludeEntrypoints) {
149-
return included;
150-
}
151-
return included.filter((entrypoint) => {
152-
return !options.excludeEntrypoints!.some((exclusion) => {
153-
if (typeof exclusion === "string") {
154-
return formatEntrypointString(exclusion, fs.packageName) === entrypoint;
155-
}
156-
return exclusion.test(entrypoint);
157-
});
158-
});
159-
}
160-
161-
function formatEntrypointString(path: string, packageName: string) {
162-
return (
163-
path === "." || path.startsWith("./")
164-
? path
165-
: path === packageName
166-
? "."
167-
: path.startsWith(`${packageName}/`)
168-
? `.${path.slice(packageName.length)}`
169-
: `./${path}`
170-
).trim();
171-
}
172-
173-
function getSubpaths(exportsObject: any): string[] {
174-
if (!exportsObject || typeof exportsObject !== "object" || Array.isArray(exportsObject)) {
175-
return [];
176-
}
177-
const keys = Object.keys(exportsObject);
178-
if (keys[0].startsWith(".")) {
179-
return keys;
180-
}
181-
return keys.flatMap((key) => getSubpaths(exportsObject[key]));
182-
}
183-
184-
function getProxyDirectories(rootDir: string, fs: Package) {
185-
return fs
186-
.listFiles()
187-
.filter((f) => f.startsWith(rootDir) && f.endsWith("package.json"))
188-
.filter((f) => {
189-
try {
190-
const packageJson = JSON.parse(fs.readFile(f));
191-
return "main" in packageJson && (!packageJson.name || packageJson.name.startsWith(fs.packageName));
192-
} catch {
193-
return false;
194-
}
195-
})
196-
.map((f) => "." + f.slice(rootDir.length).slice(0, -`/package.json`.length))
197-
.filter((f) => f !== "./")
198-
.sort();
199-
}
200-
201-
function getEntrypointInfo(
202-
packageName: string,
203-
fs: Package,
204-
hosts: CompilerHosts,
205-
options: CheckPackageOptions | undefined,
206-
): Record<string, EntrypointInfo> {
207-
const packageJson = JSON.parse(fs.readFile(`/node_modules/${packageName}/package.json`));
208-
let entrypoints = getEntrypoints(fs, packageJson.exports, options);
209-
if (fs.typesPackage) {
210-
const typesPackageJson = JSON.parse(fs.readFile(`/node_modules/${fs.typesPackage.packageName}/package.json`));
211-
const typesEntrypoints = getEntrypoints(fs, typesPackageJson.exports, options);
212-
entrypoints = unique([...entrypoints, ...typesEntrypoints]);
213-
}
214-
const result: Record<string, EntrypointInfo> = {};
215-
for (const entrypoint of entrypoints) {
216-
const resolutions: Record<ResolutionKind, EntrypointResolutionAnalysis> = {
217-
node10: getEntrypointResolution(packageName, hosts.node10, "node10", entrypoint),
218-
"node16-cjs": getEntrypointResolution(packageName, hosts.node16, "node16-cjs", entrypoint),
219-
"node16-esm": getEntrypointResolution(packageName, hosts.node16, "node16-esm", entrypoint),
220-
bundler: getEntrypointResolution(packageName, hosts.bundler, "bundler", entrypoint),
221-
};
222-
result[entrypoint] = {
223-
subpath: entrypoint,
224-
resolutions,
225-
hasTypes: Object.values(resolutions).some((r) => r.resolution?.isTypeScript),
226-
isWildcard: !!resolutions.bundler.isWildcard,
227-
};
228-
}
229-
return result;
230-
}
231-
232-
function getEntrypointResolution(
233-
packageName: string,
234-
host: CompilerHostWrapper,
235-
resolutionKind: ResolutionKind,
236-
entrypoint: string,
237-
): EntrypointResolutionAnalysis {
238-
if (entrypoint.includes("*")) {
239-
return { name: entrypoint, resolutionKind, isWildcard: true };
240-
}
241-
const moduleSpecifier = packageName + entrypoint.substring(1); // remove leading . before slash
242-
const importingFileName = resolutionKind === "node16-esm" ? "/index.mts" : "/index.ts";
243-
const resolutionMode =
244-
resolutionKind === "node16-esm"
245-
? ts.ModuleKind.ESNext
246-
: resolutionKind === "node16-cjs"
247-
? ts.ModuleKind.CommonJS
248-
: undefined;
249-
const resolution = tryResolve();
250-
const implementationResolution = tryResolve(/*noDtsResolution*/ true);
251-
const files = resolution
252-
? host
253-
.createPrimaryProgram(resolution.fileName)
254-
.getSourceFiles()
255-
.map((f) => f.fileName)
256-
: undefined;
257-
258-
return {
259-
name: entrypoint,
260-
resolutionKind,
261-
resolution,
262-
implementationResolution,
263-
files,
264-
};
265-
266-
function tryResolve(noDtsResolution?: boolean): Resolution | undefined {
267-
const { resolution, trace } = host.resolveModuleName(
268-
moduleSpecifier,
269-
importingFileName,
270-
resolutionMode,
271-
noDtsResolution,
272-
);
273-
const fileName = resolution.resolvedModule?.resolvedFileName;
274-
if (!fileName) {
275-
return undefined;
276-
}
277-
278-
return {
279-
fileName,
280-
isJson: resolution.resolvedModule.extension === ts.Extension.Json,
281-
isTypeScript: ts.hasTSFileExtension(resolution.resolvedModule.resolvedFileName),
282-
trace,
283-
};
284-
}
285-
}
286-
287-
function unique<T>(array: readonly T[]): T[] {
288-
return array.filter((value, index) => array.indexOf(value) === index);
289-
}
290-
291-
function getBuildTools(packageJson: any): Partial<Record<BuildTool, string>> {
292-
if (!packageJson.devDependencies) {
293-
return {};
294-
}
295-
const result: Partial<Record<BuildTool, string>> = {};
296-
for (const buildTool of allBuildTools) {
297-
if (buildTool in packageJson.devDependencies) {
298-
result[buildTool] = packageJson.devDependencies[buildTool];
299-
}
300-
}
301-
return result;
302-
}
303-
304-
function getModuleKinds(
305-
entrypoints: Record<string, EntrypointInfo>,
306-
resolutionOption: ResolutionOption,
307-
hosts: CompilerHosts,
308-
): Record<string, ModuleKind> {
309-
const host = hosts[resolutionOption];
310-
const result: Record<string, ModuleKind> = {};
311-
for (const resolutionKind of getResolutionKinds(resolutionOption)) {
312-
for (const entrypoint of Object.values(entrypoints)) {
313-
const resolution = entrypoint.resolutions[resolutionKind];
314-
for (const fileName of resolution.files ?? []) {
315-
if (!result[fileName]) {
316-
result[fileName] = host.getModuleKindForFile(fileName)!;
317-
}
318-
}
319-
if (resolution.implementationResolution) {
320-
const fileName = resolution.implementationResolution.fileName;
321-
if (!result[fileName]) {
322-
result[fileName] = host.getModuleKindForFile(fileName)!;
323-
}
324-
}
325-
}
326-
}
327-
return result;
328-
}

0 commit comments

Comments
 (0)