Skip to content

Commit 78eee6d

Browse files
committed
Update CLI
1 parent e8befc6 commit 78eee6d

File tree

11 files changed

+541
-128
lines changed

11 files changed

+541
-128
lines changed

‎package-lock.json‎

Lines changed: 372 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/cli/package.json‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
},
3939
"type": "module",
4040
"devDependencies": {
41+
"@types/marked": "^5.0.0",
42+
"@types/marked-terminal": "^3.1.3",
4143
"@types/node": "^20.2.5",
4244
"@types/node-fetch": "^2.6.4",
4345
"typescript": "^5.0.0-dev.20230207"
@@ -46,6 +48,8 @@
4648
"@arethetypeswrong/core": "0.0.8",
4749
"chalk": "^4.1.2",
4850
"cli-table3": "^0.6.3",
49-
"commander": "^10.0.1"
51+
"commander": "^10.0.1",
52+
"marked": "^5.1.0",
53+
"marked-terminal": "^5.2.0"
5054
}
5155
}

‎packages/cli/src/index.ts‎

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { createRequire } from "module";
1111
import * as render from "./render/index.js";
1212
import { readConfig } from "./readConfig.js";
1313
import { problemFlags } from "./problemUtils.js";
14+
import { groupProblemsByKind } from "@arethetypeswrong/core/utils";
1415

1516
const packageJson = createRequire(import.meta.url)("../package.json");
1617
const version = packageJson.version;
@@ -100,15 +101,12 @@ particularly ESM-related module resolution issues.`
100101
};
101102

102103
if (analysis.containsTypes) {
103-
result.problems = core.groupByKind(core.getProblems(analysis));
104+
result.problems = groupProblemsByKind(analysis.problems);
104105
}
105106

106107
console.log(JSON.stringify(result));
107108

108-
if (
109-
analysis.containsTypes &&
110-
core.getProblems(analysis).some((problem) => !opts.ignoreRules?.includes(problem.kind))
111-
)
109+
if (analysis.containsTypes && analysis.problems.some((problem) => !opts.ignoreRules?.includes(problem.kind)))
112110
process.exit(1);
113111

114112
return;
@@ -118,10 +116,7 @@ particularly ESM-related module resolution issues.`
118116
if (analysis.containsTypes) {
119117
await render.typed(analysis, opts);
120118

121-
if (
122-
analysis.containsTypes &&
123-
core.getProblems(analysis).some((problem) => !opts.ignoreRules?.includes(problem.kind))
124-
)
119+
if (analysis.containsTypes && analysis.problems.some((problem) => !opts.ignoreRules?.includes(problem.kind)))
125120
process.exit(1);
126121
} else {
127122
render.untyped(analysis as core.UntypedAnalysis);

‎packages/cli/src/problemUtils.ts‎

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,55 +11,8 @@ export const problemFlags: Record<ProblemKind, string> = {
1111
FallbackCondition: "fallback-condition",
1212
CJSOnlyExportsDefault: "cjs-only-exports-default",
1313
FalseExportDefault: "false-export-default",
14-
UnexpectedESMSyntax: "unexpected-esm-syntax",
15-
UnexpectedCJSSyntax: "unexpected-cjs-syntax",
16-
};
17-
18-
export const problemEmoji: Record<ProblemKind, string> = {
19-
Wildcard: "❓",
20-
NoResolution: "💀",
21-
UntypedResolution: "🚫",
22-
FalseCJS: "🎭",
23-
FalseESM: "👺",
24-
CJSResolvesToESM: "⚠️",
25-
FallbackCondition: "🐛",
26-
CJSOnlyExportsDefault: "🤨",
27-
FalseExportDefault: "❗️",
28-
UnexpectedESMSyntax: "🚭",
29-
UnexpectedCJSSyntax: "🚱",
30-
};
31-
32-
export const withEmoji: Record<ProblemKind, string> = {
33-
Wildcard: `${problemEmoji.Wildcard} Unable to check`,
34-
NoResolution: `${problemEmoji.NoResolution} Failed to resolve`,
35-
UntypedResolution: `${problemEmoji.UntypedResolution} No types`,
36-
FalseCJS: `${problemEmoji.FalseCJS} Masquerading as CJS`,
37-
FalseESM: `${problemEmoji.FalseESM} Masquerading as ESM`,
38-
CJSResolvesToESM: `${problemEmoji.CJSResolvesToESM} ESM (dynamic import only)`,
39-
FallbackCondition: `${problemEmoji.FallbackCondition} Used fallback condition`,
40-
CJSOnlyExportsDefault: `${problemEmoji.CJSOnlyExportsDefault} CJS default export`,
41-
FalseExportDefault: `${problemEmoji.FalseExportDefault} Incorrect default export`,
42-
UnexpectedESMSyntax: `${problemEmoji.UnexpectedESMSyntax} Unexpected ESM syntax`,
43-
UnexpectedCJSSyntax: `${problemEmoji.UnexpectedCJSSyntax} Unexpected CJS syntax`,
44-
};
45-
46-
export const noEmoji: Record<ProblemKind, string> = {
47-
Wildcard: `Unable to check`,
48-
NoResolution: `Failed to resolve`,
49-
UntypedResolution: `No types`,
50-
FalseCJS: `Masquerading as CJS`,
51-
FalseESM: `Masquerading as ESM`,
52-
CJSResolvesToESM: `ESM (dynamic import only)`,
53-
FallbackCondition: `Used fallback condition`,
54-
CJSOnlyExportsDefault: `CJS default export`,
55-
FalseExportDefault: `Incorrect default export`,
56-
UnexpectedESMSyntax: `Unexpected ESM syntax`,
57-
UnexpectedCJSSyntax: `Unexpected CJS syntax`,
58-
};
59-
60-
export const problemShortDescriptions = {
61-
emoji: withEmoji,
62-
noEmoji: noEmoji,
14+
UnexpectedModuleSyntax: "unexpected-module-syntax",
15+
InternalResolutionError: "internal-resolution-error",
6316
};
6417

6518
export const resolutionKinds: Record<core.ResolutionKind, string> = {

‎packages/cli/src/render/typed.ts‎

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
import * as core from "@arethetypeswrong/core";
2-
import { allResolutionKinds } from "@arethetypeswrong/core/utils";
3-
import Table, { type GenericTable, type HorizontalTableRow } from "cli-table3";
2+
import { allResolutionKinds, groupProblemsByKind } from "@arethetypeswrong/core/utils";
43
import chalk from "chalk";
4+
import Table, { type GenericTable, type HorizontalTableRow } from "cli-table3";
5+
import { marked } from "marked";
56

6-
import { moduleKinds, problemEmoji, resolutionKinds, problemShortDescriptions, problemFlags } from "../problemUtils.js";
7+
import { filterProblems, problemAffectsEntrypoint, problemKindInfo } from "@arethetypeswrong/core/problems";
78
import type { Opts } from "../index.js";
9+
import { moduleKinds, problemFlags, resolutionKinds } from "../problemUtils.js";
810
import { tableFlipped } from "./tableFlipped.js";
11+
import TerminalRenderer from "marked-terminal";
912

1013
export async function typed(analysis: core.TypedAnalysis, opts: Opts) {
11-
const problems = core
12-
.getProblems(analysis)
13-
.filter((problem) => !opts.ignoreRules || !opts.ignoreRules.includes(problem.kind));
14-
15-
const subpaths = Object.keys(analysis.entrypointResolutions);
14+
const problems = analysis.problems.filter((problem) => !opts.ignoreRules || !opts.ignoreRules.includes(problem.kind));
15+
const grouped = groupProblemsByKind(problems);
16+
const subpaths = Object.keys(analysis.entrypoints);
17+
marked.setOptions({
18+
// @ts-expect-error the types are wrong (haha)
19+
renderer: new TerminalRenderer(),
20+
mangle: false,
21+
headerIds: false,
22+
});
1623

1724
if (opts.ignoreRules && opts.ignoreRules.length) {
1825
console.log(
@@ -25,23 +32,18 @@ export async function typed(analysis: core.TypedAnalysis, opts: Opts) {
2532
}
2633

2734
if (opts.summary) {
28-
const summaries = core.summarizeProblems(problems, analysis);
2935
const defaultSummary = !opts.emoji ? " No problems found" : " No problems found 🌟";
30-
const summaryTexts = summaries.map((summary) => {
31-
return summary.messages
32-
.map((message) => {
33-
const messageText = message.messageText.split(". ").join(".\n ");
34-
if (!opts.emoji) return ` ${messageText}`;
35-
return ` ${problemEmoji[summary.kind]} ${messageText}`;
36-
})
37-
.join("\n");
36+
const summaryTexts = Object.keys(grouped).map((kind) => {
37+
const emoji = opts.emoji ? `${problemKindInfo[kind as core.ProblemKind].emoji} ` : "";
38+
const description = marked(problemKindInfo[kind as core.ProblemKind].description);
39+
return `${emoji}${description}`;
3840
});
3941

4042
console.log((summaryTexts.join("\n\n") || defaultSummary) + "\n");
4143
}
4244

4345
const entrypoints = subpaths.map((s) => {
44-
const hasProblems = problems.some((p) => p.entrypoint === s);
46+
const hasProblems = problems.some((p) => problemAffectsEntrypoint(p, s, analysis));
4547
const color = hasProblems ? "redBright" : "greenBright";
4648

4749
if (s === ".") return chalk.bold[color](`"${analysis.packageName}"`);
@@ -60,22 +62,21 @@ export async function typed(analysis: core.TypedAnalysis, opts: Opts) {
6062

6163
row = row.concat(
6264
allResolutionKinds.map((kind) => {
63-
const problemsForCell = problems.filter(
64-
(problem) => problem.entrypoint === subpath && problem.resolutionKind === kind
65+
const problemsForCell = groupProblemsByKind(
66+
filterProblems(problems, analysis, { entrypoint: subpath, resolutionKind: kind })
6567
);
66-
67-
const resolution = analysis.entrypointResolutions[subpath][kind].resolution;
68-
69-
const descriptions = problemShortDescriptions[!opts.emoji ? "noEmoji" : "emoji"];
70-
71-
if (problemsForCell.length) {
72-
return problemsForCell.map((problem) => descriptions[problem.kind]).join("\n");
68+
const resolution = analysis.entrypoints[subpath].resolutions[kind].resolution;
69+
const kinds = Object.keys(problemsForCell) as core.ProblemKind[];
70+
if (kinds.length) {
71+
return kinds
72+
.map(
73+
(kind) => (opts.emoji ? `${problemKindInfo[kind].emoji} ` : "") + problemKindInfo[kind].shortDescription
74+
)
75+
.join("\n");
7376
}
7477

7578
const jsonResult = !opts.emoji ? "OK (JSON)" : "🟢 (JSON)";
76-
7779
const moduleResult = (!opts.emoji ? "OK " : "🟢 ") + moduleKinds[resolution?.moduleKind?.detectedKind || ""];
78-
7980
return resolution?.isJson ? jsonResult : moduleResult;
8081
})
8182
);
@@ -96,22 +97,21 @@ export async function typed(analysis: core.TypedAnalysis, opts: Opts) {
9697

9798
row = row.concat(
9899
subpaths.map((subpath) => {
99-
const problemsForCell = problems.filter(
100-
(problem) => problem.entrypoint === subpath && problem.resolutionKind === kind
100+
const problemsForCell = groupProblemsByKind(
101+
filterProblems(problems, analysis, { entrypoint: subpath, resolutionKind: kind })
101102
);
102-
103-
const resolution = analysis.entrypointResolutions[subpath][kind].resolution;
104-
105-
const descriptions = problemShortDescriptions[!opts.emoji ? "noEmoji" : "emoji"];
106-
107-
if (problemsForCell.length) {
108-
return problemsForCell.map((problem) => descriptions[problem.kind]).join("\n");
103+
const resolution = analysis.entrypoints[subpath].resolutions[kind].resolution;
104+
const kinds = Object.keys(problemsForCell) as core.ProblemKind[];
105+
if (kinds.length) {
106+
return kinds
107+
.map(
108+
(kind) => (opts.emoji ? `${problemKindInfo[kind].emoji} ` : "") + problemKindInfo[kind].shortDescription
109+
)
110+
.join("\n");
109111
}
110112

111113
const jsonResult = !opts.emoji ? "OK (JSON)" : "🟢 (JSON)";
112-
113114
const moduleResult = (!opts.emoji ? "OK " : "🟢 ") + moduleKinds[resolution?.moduleKind?.detectedKind || ""];
114-
115115
return resolution?.isJson ? jsonResult : moduleResult;
116116
})
117117
);

‎packages/core/src/problems.ts‎

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import type { ProblemKind } from "./types.js";
1+
import type { Problem, ProblemKind, ResolutionKind, ResolutionOption, TypedAnalysis } from "./types.js";
2+
import {
3+
allResolutionKinds,
4+
getResolutionKinds,
5+
getResolutionOption,
6+
isEntrypointResolutionProblem,
7+
isFileProblem,
8+
isResolutionBasedFileProblem,
9+
} from "./utils.js";
210

311
export interface ProblemKindInfo {
412
title: string;
@@ -81,3 +89,86 @@ export const problemKindInfo: Record<ProblemKind, ProblemKindInfo> = {
8189
"Import found in a type declaration file failed to resolve. Either this indicates that runtime resolution errors will occur, or (more likely) the types misrepresent the contents of the JavaScript files.",
8290
},
8391
};
92+
93+
export interface ProblemFilter {
94+
kind?: ProblemKind;
95+
entrypoint?: string;
96+
resolutionKind?: ResolutionKind;
97+
resolutionOption?: ResolutionOption;
98+
}
99+
100+
export function filterProblems(analysis: TypedAnalysis, filter: ProblemFilter): Problem[];
101+
export function filterProblems(problems: readonly Problem[], analysis: TypedAnalysis, filter: ProblemFilter): Problem[];
102+
export function filterProblems(
103+
...args:
104+
| [analysis: TypedAnalysis, filter: ProblemFilter]
105+
| [problems: readonly Problem[], analysis: TypedAnalysis, filter: ProblemFilter]
106+
) {
107+
const [problems, analysis, filter] = args.length === 2 ? [args[0].problems, ...args] : args;
108+
return problems.filter((p) => {
109+
if (filter.kind && p.kind !== filter.kind) {
110+
return false;
111+
}
112+
if (filter.entrypoint && filter.resolutionKind) {
113+
return problemAffectsEntrypointResolution(p, filter.entrypoint, filter.resolutionKind, analysis);
114+
}
115+
if (filter.entrypoint && filter.resolutionOption) {
116+
return getResolutionKinds(filter.resolutionOption).every((resolutionKind) =>
117+
problemAffectsEntrypointResolution(p, filter.entrypoint!, resolutionKind, analysis)
118+
);
119+
}
120+
if (filter.entrypoint) {
121+
return problemAffectsEntrypoint(p, filter.entrypoint, analysis);
122+
}
123+
if (filter.resolutionKind) {
124+
return problemAffectsResolutionKind(p, filter.resolutionKind, analysis);
125+
}
126+
return true;
127+
});
128+
}
129+
130+
export function problemAffectsResolutionKind(
131+
problem: Problem,
132+
resolutionKind: ResolutionKind,
133+
analysis: TypedAnalysis
134+
) {
135+
if (isEntrypointResolutionProblem(problem)) {
136+
return problem.resolutionKind === resolutionKind;
137+
}
138+
if (isResolutionBasedFileProblem(problem)) {
139+
return problem.resolutionOption === getResolutionOption(resolutionKind);
140+
}
141+
return Object.values(analysis.entrypoints).some((entrypointInfo) =>
142+
entrypointInfo.resolutions[resolutionKind].files?.includes(problem.fileName)
143+
);
144+
}
145+
146+
export function problemAffectsEntrypoint(problem: Problem, entrypoint: string, analysis: TypedAnalysis) {
147+
if (isEntrypointResolutionProblem(problem)) {
148+
return problem.entrypoint === entrypoint;
149+
}
150+
return allResolutionKinds.some((resolutionKind) =>
151+
analysis.entrypoints[entrypoint].resolutions[resolutionKind].files?.includes(problem.fileName)
152+
);
153+
}
154+
155+
export function problemAffectsEntrypointResolution(
156+
problem: Problem,
157+
entrypoint: string,
158+
resolutionKind: ResolutionKind,
159+
analysis: TypedAnalysis
160+
) {
161+
if (isEntrypointResolutionProblem(problem)) {
162+
return problem.entrypoint === entrypoint && problem.resolutionKind === resolutionKind;
163+
}
164+
if (isResolutionBasedFileProblem(problem)) {
165+
return (
166+
getResolutionOption(resolutionKind) === problem.resolutionOption &&
167+
analysis.entrypoints[entrypoint].resolutions[resolutionKind].files?.includes(problem.fileName)
168+
);
169+
}
170+
if (isFileProblem(problem)) {
171+
return analysis.entrypoints[entrypoint].resolutions[resolutionKind].files?.includes(problem.fileName);
172+
}
173+
throw new Error(`Unhandled problem type '${(problem satisfies never as Problem).kind}'`);
174+
}

‎packages/core/src/utils.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ export function getResolutionOption(resolutionKind: ResolutionKind): ResolutionO
2525
}
2626
}
2727

28+
export function getResolutionKinds(resolutionOption: ResolutionOption): ResolutionKind[] {
29+
switch (resolutionOption) {
30+
case "node10":
31+
return ["node10"];
32+
case "node16":
33+
return ["node16-cjs", "node16-esm"];
34+
case "bundler":
35+
return ["bundler"];
36+
}
37+
}
38+
2839
export function isDefined<T>(value: T | undefined): value is T {
2940
return value !== undefined;
3041
}

‎packages/web/package.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
"dependencies": {
1818
"@arethetypeswrong/core": "file:../core",
1919
"immer": "^9.0.21",
20+
"marked": "^5.1.0",
2021
"nprogress": "^0.2.0"
2122
},
2223
"devDependencies": {
24+
"@types/marked": "^5.0.0",
2325
"@types/nprogress": "^0.2.0",
2426
"esbuild": "^0.17.4",
2527
"vite": "^4.0.4"

‎packages/web/src/main.css‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ dt,
116116
dd {
117117
padding: 0.5em 0;
118118
}
119+
dd > p {
120+
margin: 0;
121+
}
119122
#resolutions {
120123
margin-bottom: 1rem;
121124
font-size: small;

0 commit comments

Comments
 (0)