@r_masseater/msch 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/{cli-contract-Wo9XK0jk.mjs → cli-contract-CkYucdU4.mjs} +18 -9
- package/dist/cli-contract-CkYucdU4.mjs.map +1 -0
- package/dist/cli.mjs +196 -21
- package/dist/cli.mjs.map +1 -1
- package/dist/lint.mjs +19 -1
- package/dist/lint.mjs.map +1 -1
- package/dist/oxlint.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli-contract-Wo9XK0jk.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -38,5 +38,9 @@ oxlint のバージョンは、@effect/tsgo が対応するものに合わせる
|
|
|
38
38
|
|
|
39
39
|
## msch の検査
|
|
40
40
|
|
|
41
|
-
リポジトリのルートで `msch` を実行すると、値の集合と形の重複、overrides
|
|
41
|
+
リポジトリのルートで `msch` を実行すると、値の集合と形の重複、overrides の要不要、テストのためだけのコード、見えない文字、Python の使用、ルート以外の案内文書を調べる。
|
|
42
|
+
外部から読んだ値を Effect Schema で確かめるときは、`satisfies Schema.Decoder<ライブラリの型>` か変数の型注釈でライブラリの型に結びつける。結びつけた Schema は、ライブラリの形の写しとして指摘しない。
|
|
43
|
+
見えない文字は、ゼロ幅の文字・書字方向を変える制御文字・タグ文字のように、エディタやレビューで見えずに意味を変えうる文字で、`git ls-files` に載るテキストのファイルから探す。ファイル先頭の BOM と、NUL を含むバイナリは対象にしない。
|
|
44
|
+
Python は使わない方針なので、`.py`・`pyproject.toml`・`uv.lock` などのファイルを指摘する。スクリプト・CI・設定の中で `python3`・`pip`・`uv`・`poetry` を呼ぶ箇所と、`actions/setup-python` などの action、`mise.toml` に書いた Python の道具も指摘する。
|
|
45
|
+
案内文書は、`AGENTS.md`・`CLAUDE.md` をルートにだけ置かせ、README はルートか、`private` でない package.json の隣(npm のページに出るもの)にだけ置かせる。
|
|
42
46
|
指摘があれば終了コードは 1 になる。`--json` を付けると結果を JSON で出す。
|
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
import { Array, Context, Effect, FileSystem, Layer, Option, Order, Path, Predicate, Record, Schema, Stream, String as String$1, pipe } from "effect";
|
|
2
2
|
import { InternalSymbolName, SyntaxKind, getJSDocTags, getLeadingCommentRanges, getNameOfDeclaration, getTextOfJSDocComment } from "typescript/ast";
|
|
3
|
-
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isElementAccessExpression, isEnumDeclaration, isExportDeclaration, isIdentifier, isImportDeclaration, isInterfaceDeclaration, isModuleBlock, isModuleDeclaration, isNewExpression, isNumericLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSignatureDeclaration, isSourceFile, isStringLiteral, isTypeAliasDeclaration, isTypeLiteralNode, isTypeReferenceNode, isUnionTypeNode, isVariableDeclaration, isVariableDeclarationList, isVariableStatement } from "typescript/ast/is";
|
|
3
|
+
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isElementAccessExpression, isEnumDeclaration, isExportDeclaration, isIdentifier, isImportDeclaration, isInterfaceDeclaration, isModuleBlock, isModuleDeclaration, isNewExpression, isNumericLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isSignatureDeclaration, isSourceFile, isSpreadElement, isStringLiteral, isTypeAliasDeclaration, isTypeLiteralNode, isTypeReferenceNode, isUnionTypeNode, isVariableDeclaration, isVariableDeclarationList, isVariableStatement } from "typescript/ast/is";
|
|
4
4
|
import { API, IndexKind } from "typescript/sync";
|
|
5
5
|
import { layer } from "@effect/platform-node/NodeServices";
|
|
6
6
|
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
7
7
|
// src/repository-files.ts
|
|
8
8
|
const GENERATED_PATHSPEC = ":(attr:linguist-generated)";
|
|
9
|
+
const LISTED_ARGS = [
|
|
10
|
+
"--cached",
|
|
11
|
+
"--others",
|
|
12
|
+
"--exclude-standard"
|
|
13
|
+
];
|
|
9
14
|
var RepositoryFiles = class extends Context.Service()("@r_masseater/msch/repository-files/RepositoryFiles") {};
|
|
10
15
|
const nodeRepositoryFiles = Layer.effect(RepositoryFiles, Effect.gen(function* nodeRepositoryFiles() {
|
|
11
16
|
const fs = yield* FileSystem.FileSystem;
|
|
12
17
|
const path = yield* Path.Path;
|
|
13
18
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
14
|
-
const
|
|
19
|
+
const gitFiles = (root, args) => Effect.gen(function* listFiles() {
|
|
15
20
|
const listed = yield* (yield* ChildProcess.make("git", [
|
|
16
21
|
"ls-files",
|
|
17
22
|
"-z",
|
|
18
|
-
|
|
19
|
-
GENERATED_PATHSPEC
|
|
23
|
+
...args
|
|
20
24
|
], {
|
|
21
25
|
cwd: root,
|
|
22
26
|
stdin: "ignore"
|
|
23
27
|
})).stdout.pipe(Stream.decodeText(), Stream.mkString);
|
|
24
|
-
return pipe(String$1.split(listed, "\0"), Array.filter(String$1.isNonEmpty)
|
|
28
|
+
return pipe(String$1.split(listed, "\0"), Array.filter(String$1.isNonEmpty));
|
|
25
29
|
}).pipe(Effect.scoped, Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.orElseSucceed(() => []));
|
|
26
30
|
return RepositoryFiles.of({
|
|
27
|
-
generatedFiles,
|
|
31
|
+
generatedFiles: (root) => gitFiles(root, ["--", GENERATED_PATHSPEC]).pipe(Effect.map(Array.map((file) => path.join(root, file)))),
|
|
32
|
+
listedFiles: (root) => gitFiles(root, LISTED_ARGS),
|
|
28
33
|
readFile: (file) => fs.readFileString(file).pipe(Effect.option),
|
|
29
34
|
exists: (file) => fs.exists(file).pipe(Effect.orElseSucceed(() => false)),
|
|
30
35
|
realPath: (file) => fs.realPath(file).pipe(Effect.orElseSucceed(() => file)),
|
|
@@ -230,7 +235,11 @@ const RuleNameSchema = Schema.Literals([
|
|
|
230
235
|
"library-value-set",
|
|
231
236
|
"library-shape",
|
|
232
237
|
"unused-override",
|
|
233
|
-
"removable-override"
|
|
238
|
+
"removable-override",
|
|
239
|
+
"hidden-unicode",
|
|
240
|
+
"python-file",
|
|
241
|
+
"python-command",
|
|
242
|
+
"nested-docs"
|
|
234
243
|
]);
|
|
235
244
|
const ProblemSchema = Schema.Struct({
|
|
236
245
|
file: Schema.String,
|
|
@@ -266,6 +275,6 @@ const describeFinding = (problem) => `[${problem.rule}] ${problem.message}`;
|
|
|
266
275
|
const formatProblem = (relative) => (problem) => `${relative(problem.file)}:${String(problem.line)}:${String(problem.column)} ${describeFinding(problem)}`;
|
|
267
276
|
// src/cli-contract.ts
|
|
268
277
|
const JSON_FLAG = "--json";
|
|
269
|
-
export {
|
|
278
|
+
export { isSatisfiesExpression as $, API as A, isCallExpression as B, MANIFEST_FILE as C, workspaceDependencies as D, readManifest as E, getLeadingCommentRanges as F, isInterfaceDeclaration as G, isElementAccessExpression as H, getNameOfDeclaration as I, isNewExpression as J, isModuleBlock as K, getTextOfJSDocComment as L, InternalSymbolName as M, SyntaxKind as N, RepositoryFiles as O, getJSDocTags as P, isPropertyAssignment as Q, isArrayLiteralExpression as R, uniqueByReference as S, dependencyEntries as T, isEnumDeclaration as U, isClassDeclaration as V, isIdentifier as W, isObjectLiteralExpression as X, isNumericLiteral as Y, isPropertyAccessExpression as Z, lineOf as _, problemAt as a, isTypeLiteralNode as at, resolvedSymbol as b, childrenOf as c, isVariableDeclaration as ct, declarationsOf as d, isSignatureDeclaration as et, descendantsOf as f, isTypeDeclaration as g, isGenericDeclaration as h, formatProblem as i, isTypeAliasDeclaration as it, IndexKind as j, nodeRepositoryFiles as k, columnOf as l, isVariableDeclarationList as lt, isExternalDeclaration as m, ProblemList as n, isSpreadElement as nt, sortProblems as o, isTypeReferenceNode as ot, importedModulesOf as p, isModuleDeclaration as q, describeFinding as r, isStringLiteral as rt, NODE_MODULES_SEGMENT as s, isUnionTypeNode as st, JSON_FLAG as t, isSourceFile as tt, contains as u, isVariableStatement as ut, propertyNamesOf as v, NODE_MODULES as w, symbolAt as x, referencedSymbols as y, isBinaryExpression as z };
|
|
270
279
|
|
|
271
|
-
//# sourceMappingURL=cli-contract-
|
|
280
|
+
//# sourceMappingURL=cli-contract-CkYucdU4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-contract-CkYucdU4.mjs","names":["Str","Arr","nodeServicesLayer","Rec","Arr","Str","Arr","Str","Arr"],"sources":["../src/repository-files.ts","../src/dependencies.ts","../src/syntax.ts","../src/problem.ts","../src/cli-contract.ts"],"sourcesContent":["import { layer as nodeServicesLayer } from \"@effect/platform-node/NodeServices\";\nimport {\n Array as Arr,\n Context,\n Effect,\n FileSystem,\n Layer,\n Path,\n Stream,\n String as Str,\n pipe,\n} from \"effect\";\nimport type { Option } from \"effect\";\nimport { ChildProcess, ChildProcessSpawner } from \"effect/process\";\n\nconst GENERATED_PATHSPEC = \":(attr:linguist-generated)\";\nconst LISTED_ARGS = [\"--cached\", \"--others\", \"--exclude-standard\"];\n\nclass RepositoryFiles extends Context.Service<\n RepositoryFiles,\n {\n readonly readFile: (file: string) => Effect.Effect<Option.Option<string>>;\n readonly exists: (file: string) => Effect.Effect<boolean>;\n readonly realPath: (file: string) => Effect.Effect<string>;\n readonly readDirectory: (directory: string) => Effect.Effect<readonly string[]>;\n readonly generatedFiles: (root: string) => Effect.Effect<readonly string[]>;\n readonly listedFiles: (root: string) => Effect.Effect<readonly string[]>;\n readonly isDirectory: (file: string) => Effect.Effect<Option.Option<boolean>>;\n readonly join: (...segments: readonly string[]) => string;\n readonly resolve: (...segments: readonly string[]) => string;\n readonly relative: (from: string, to: string) => string;\n readonly dirname: (file: string) => string;\n readonly basename: (file: string) => string;\n }\n>()(\"@r_masseater/msch/repository-files/RepositoryFiles\") {}\n\nconst nodeRepositoryFiles = Layer.effect(\n RepositoryFiles,\n Effect.gen(function* nodeRepositoryFiles() {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n const gitFiles = (root: string, args: readonly string[]): Effect.Effect<readonly string[]> =>\n Effect.gen(function* listFiles() {\n const handle = yield* ChildProcess.make(\"git\", [\"ls-files\", \"-z\", ...args], {\n cwd: root,\n stdin: \"ignore\",\n });\n const listed = yield* handle.stdout.pipe(Stream.decodeText(), Stream.mkString);\n return pipe(Str.split(listed, \"\\0\"), Arr.filter(Str.isNonEmpty));\n }).pipe(\n Effect.scoped,\n Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),\n Effect.orElseSucceed((): readonly string[] => []),\n );\n return RepositoryFiles.of({\n generatedFiles: (root) =>\n gitFiles(root, [\"--\", GENERATED_PATHSPEC]).pipe(\n Effect.map(Arr.map((file) => path.join(root, file))),\n ),\n listedFiles: (root) => gitFiles(root, LISTED_ARGS),\n readFile: (file) => fs.readFileString(file).pipe(Effect.option),\n exists: (file) => fs.exists(file).pipe(Effect.orElseSucceed(() => false)),\n realPath: (file) => fs.realPath(file).pipe(Effect.orElseSucceed(() => file)),\n readDirectory: (directory) =>\n fs.readDirectory(directory).pipe(Effect.orElseSucceed((): readonly string[] => [])),\n isDirectory: (file) =>\n fs.stat(file).pipe(\n Effect.map((info) => info.type === \"Directory\"),\n Effect.option,\n ),\n join: (...segments) => path.join(...segments),\n resolve: (...segments) => path.resolve(...segments),\n relative: (from, to) => path.relative(from, to),\n dirname: (file) => path.dirname(file),\n basename: (file) => path.basename(file),\n });\n }),\n).pipe(Layer.provide(nodeServicesLayer));\n\nexport { RepositoryFiles, nodeRepositoryFiles };\n","import {\n Array as Arr,\n Effect,\n Option,\n Order,\n Predicate,\n Schema,\n String as Str,\n Record as Rec,\n pipe,\n} from \"effect\";\n\nimport { RepositoryFiles } from \"./repository-files.ts\";\n\ninterface DependencyEntry {\n readonly packageName: string;\n readonly specifier: string;\n readonly declarationsPath: string;\n}\n\nconst DependencyMap = Schema.optional(Schema.Record(Schema.String, Schema.String));\n\nconst PackageManifest = Schema.Struct({\n name: Schema.optional(Schema.String),\n dependencies: DependencyMap,\n devDependencies: DependencyMap,\n peerDependencies: DependencyMap,\n exports: Schema.optional(Schema.Unknown),\n types: Schema.optional(Schema.String),\n typings: Schema.optional(Schema.String),\n main: Schema.optional(Schema.String),\n});\n\ntype Manifest = typeof PackageManifest.Type;\n\nconst MANIFEST_FILE = \"package.json\";\nconst NODE_MODULES = \"node_modules\";\nconst WORKSPACE_PROTOCOL = \"workspace:\";\nconst ROOT_SUBPATH = \".\";\nconst SUBPATH_PREFIX = \"./\";\nconst WILDCARD = \"*\";\nconst TYPES_CONDITION = \"types\";\nconst TYPES_SCOPE = \"@types/\";\nconst TYPES_SCOPE_SEPARATOR = \"__\";\nconst CONDITION_DEPTH_LIMIT = 8;\nconst ROOT_DEPTH = 0;\nconst DEPTH_STEP = 1;\nconst DECLARATIONS_SUFFIX = /\\.d\\.[cm]?ts$|\\.[cm]?tsx?$/u;\nconst SCRIPT_SUFFIX = /\\.(?<module>[cm]?)js$/u;\n\nconst readManifest = Effect.fn(\"readManifest\")(function* readManifest(directory: string) {\n const files = yield* RepositoryFiles;\n const manifestText = yield* files.readFile(files.join(directory, MANIFEST_FILE));\n const text = yield* Effect.fromOption(manifestText);\n return yield* Schema.decodeEffect(Schema.fromJsonString(PackageManifest))(text);\n}, Effect.option);\n\nconst conditionsOf = (held: unknown): readonly (readonly [string, unknown])[] => {\n if (!Predicate.isObject(held)) {\n return [];\n }\n return Rec.toEntries(held);\n};\n\nconst typesConditionIn = (held: unknown, depth: number): Option.Option<string> => {\n if (depth > CONDITION_DEPTH_LIMIT) {\n return Option.none();\n }\n const entries = conditionsOf(held);\n const declared = Arr.findFirst(entries, ([condition, target]) =>\n Option.liftPredicate(\n target,\n (candidate) => condition === TYPES_CONDITION && Predicate.isString(candidate),\n ),\n ).pipe(Option.filter(Predicate.isString));\n return Option.orElse(declared, () =>\n Arr.findFirst(entries, ([, nested]) => typesConditionIn(nested, depth + DEPTH_STEP)),\n );\n};\n\nconst anyConditionIn = (held: unknown, depth: number): Option.Option<string> => {\n if (Predicate.isString(held)) {\n return Option.some(held);\n }\n if (depth > CONDITION_DEPTH_LIMIT) {\n return Option.none();\n }\n return Arr.findFirst(conditionsOf(held), ([, nested]) =>\n anyConditionIn(nested, depth + DEPTH_STEP),\n );\n};\n\nconst targetPathsOf = (target: unknown): readonly string[] =>\n Arr.getSomes([typesConditionIn(target, ROOT_DEPTH), anyConditionIn(target, ROOT_DEPTH)]);\n\ninterface SubpathTarget {\n readonly subpath: string;\n readonly target: unknown;\n}\n\nconst subpathTargetsOf = (exportsField: unknown): readonly SubpathTarget[] => {\n if (Predicate.isString(exportsField)) {\n return [{ subpath: ROOT_SUBPATH, target: exportsField }];\n }\n const entries = conditionsOf(exportsField);\n if (\n !pipe(\n entries,\n Arr.some(([subpath]) => Str.startsWith(ROOT_SUBPATH)(subpath)),\n )\n ) {\n return Arr.match(entries, {\n onEmpty: () => [],\n onNonEmpty: () => [{ subpath: ROOT_SUBPATH, target: exportsField }],\n });\n }\n return pipe(\n entries,\n Arr.filter(([subpath]) => subpath === ROOT_SUBPATH || Str.startsWith(SUBPATH_PREFIX)(subpath)),\n Arr.filter(([subpath]) => !Str.includes(WILDCARD)(subpath)),\n Arr.map(([subpath, target]) => ({ subpath, target })),\n );\n};\n\nconst declarationsCandidateFor = (entryPath: string): Option.Option<string> => {\n if (DECLARATIONS_SUFFIX.test(entryPath)) {\n return Option.some(entryPath);\n }\n if (!SCRIPT_SUFFIX.test(entryPath)) {\n return Option.none();\n }\n return Option.some(Str.replace(SCRIPT_SUFFIX, \".d.$<module>ts\")(entryPath));\n};\n\nconst firstDeclarationsAmong = Effect.fn(\"firstDeclarationsAmong\")(function* firstDeclarations(\n directory: string,\n entryPaths: readonly string[],\n) {\n const files = yield* RepositoryFiles;\n const candidates = Arr.getSomes(\n pipe(\n entryPaths,\n Arr.map((entryPath) => declarationsCandidateFor(entryPath)),\n ),\n );\n const existing = yield* Effect.filter(\n pipe(\n candidates,\n Arr.map((candidate) => files.join(directory, candidate)),\n ),\n (candidate) => files.exists(candidate),\n );\n return yield* Option.match(Arr.head(existing), {\n onNone: (): Effect.Effect<Option.Option<string>> => Effect.succeedNone,\n onSome: (candidate) => files.realPath(candidate).pipe(Effect.asSome),\n });\n});\n\nconst importNameOf = (packageName: string): string => {\n if (!Str.startsWith(TYPES_SCOPE)(packageName)) {\n return packageName;\n }\n const unscoped = Str.slice(TYPES_SCOPE.length)(packageName);\n if (!Str.includes(TYPES_SCOPE_SEPARATOR)(unscoped)) {\n return unscoped;\n }\n return `@${Str.replace(TYPES_SCOPE_SEPARATOR, \"/\")(unscoped)}`;\n};\n\nconst specifierFor = (packageName: string, subpath: string): string => {\n const importName = importNameOf(packageName);\n if (subpath === ROOT_SUBPATH) {\n return importName;\n }\n return `${importName}/${Str.slice(SUBPATH_PREFIX.length)(subpath)}`;\n};\n\nconst rootEntryPaths = (manifest: Manifest): readonly string[] =>\n Arr.getSomes([\n Option.fromUndefinedOr(manifest.types),\n Option.fromUndefinedOr(manifest.typings),\n Option.fromUndefinedOr(manifest.main),\n ]);\n\nconst subpathEntriesOf = Effect.fn(\"subpathEntriesOf\")(function* subpathEntriesOf(\n packageName: string,\n directory: string,\n manifest: Manifest,\n) {\n const found = yield* Effect.forEach(subpathTargetsOf(manifest.exports), ({ subpath, target }) =>\n firstDeclarationsAmong(directory, targetPathsOf(target)).pipe(\n Effect.map(\n Option.map((declarationsPath) => ({\n packageName,\n specifier: specifierFor(packageName, subpath),\n declarationsPath,\n })),\n ),\n ),\n );\n return Arr.getSomes(found);\n});\n\nconst entriesOfDependency = Effect.fn(\"entriesOfDependency\")(function* entriesOfDependency(\n packageName: string,\n directory: string,\n) {\n const manifest = yield* readManifest(directory);\n if (Option.isNone(manifest)) {\n return [];\n }\n const subpathEntries = yield* subpathEntriesOf(packageName, directory, manifest.value);\n const rootSpecifier = specifierFor(packageName, ROOT_SUBPATH);\n if (\n pipe(\n subpathEntries,\n Arr.some((entry) => entry.specifier === rootSpecifier),\n )\n ) {\n return subpathEntries;\n }\n const rootDeclarations = yield* firstDeclarationsAmong(directory, rootEntryPaths(manifest.value));\n const rootEntries = rootDeclarations.pipe(\n Option.map((declarationsPath) => ({ packageName, specifier: rootSpecifier, declarationsPath })),\n Option.toArray,\n );\n return [...rootEntries, ...subpathEntries];\n});\n\nconst declaredDependencies = (manifest: Manifest): readonly string[] =>\n pipe(\n Rec.toEntries({\n ...manifest.dependencies,\n ...manifest.devDependencies,\n ...manifest.peerDependencies,\n }),\n Arr.filter(([, range]) => !Str.startsWith(WORKSPACE_PROTOCOL)(range)),\n Arr.map(([packageName]) => packageName),\n );\n\nconst workspaceDependencies = (manifest: Manifest): readonly string[] =>\n pipe(\n Rec.toEntries({ ...manifest.dependencies, ...manifest.devDependencies }),\n Arr.filter(([, range]) => Str.startsWith(WORKSPACE_PROTOCOL)(range)),\n Arr.map(([packageName]) => packageName),\n );\n\nconst entryOrder = Order.mapInput(Order.String, (entry: DependencyEntry) => entry.specifier);\n\nconst dependencyEntries = Effect.fn(\"dependencyEntries\")(function* dependencyEntries(\n packageDirectory: string,\n) {\n const files = yield* RepositoryFiles;\n const manifest = yield* readManifest(packageDirectory);\n if (Option.isNone(manifest)) {\n return [];\n }\n const entries = yield* Effect.forEach(declaredDependencies(manifest.value), (packageName) =>\n entriesOfDependency(packageName, files.join(packageDirectory, NODE_MODULES, packageName)),\n );\n return Arr.sort(\n Arr.dedupeWith(entries.flat(), (left, right) => left.specifier === right.specifier),\n entryOrder,\n );\n});\n\nexport { MANIFEST_FILE, NODE_MODULES, dependencyEntries, readManifest, workspaceDependencies };\nexport type { DependencyEntry, Manifest };\n","import { Array as Arr, Option, String as Str, pipe } from \"effect\";\n\nimport {\n isEnumDeclaration,\n isExportDeclaration,\n isIdentifier,\n isImportDeclaration,\n isInterfaceDeclaration,\n isStringLiteral,\n isTypeAliasDeclaration,\n SyntaxKind,\n} from \"./compiler.ts\";\nimport type {\n Checker,\n Declaration,\n EnumDeclaration,\n InterfaceDeclaration,\n Node,\n Program,\n SourceFile,\n TypeSymbol,\n Type,\n TypeAliasDeclaration,\n} from \"./compiler.ts\";\nimport { NODE_MODULES } from \"./dependencies.ts\";\n\nconst NODE_MODULES_SEGMENT = `/${NODE_MODULES}/`;\n\nconst POSITION_BASE = 1;\n\nconst uniqueByReference = <Item>(items: readonly Item[]): readonly Item[] =>\n Arr.dedupeWith(items, (left, right) => left === right);\n\nconst childrenOf = (node: Node): readonly Node[] => {\n const children: Node[] = [];\n node.forEachChild((child) => {\n children.push(child);\n });\n return children;\n};\n\nconst descendantsOf = (node: Node): readonly Node[] =>\n pipe(\n childrenOf(node),\n Arr.flatMap((child) => Arr.prepend(descendantsOf(child), child)),\n );\n\nconst isExternalFile =\n (program: Program) =>\n (sourceFile: SourceFile): boolean =>\n Str.includes(NODE_MODULES_SEGMENT)(sourceFile.fileName) ||\n program.isSourceFileFromExternalLibrary(sourceFile);\n\nconst isExternalDeclaration =\n (program: Program) =>\n (declaration: Node): boolean =>\n isExternalFile(program)(declaration.getSourceFile());\n\nconst ALIAS_KINDS: ReadonlySet<SyntaxKind> = new Set([\n SyntaxKind.ImportSpecifier,\n SyntaxKind.ImportClause,\n SyntaxKind.NamespaceImport,\n SyntaxKind.ExportSpecifier,\n SyntaxKind.ImportEqualsDeclaration,\n SyntaxKind.ExportAssignment,\n]);\n\nconst declarationsOf = (symbol: TypeSymbol): readonly Declaration[] =>\n pipe(\n symbol.declarations,\n Arr.flatMap((handle) => Option.toArray(Option.fromUndefinedOr(handle.resolve()))),\n );\n\nconst isAliasDeclaration = (declaration: Node): boolean => ALIAS_KINDS.has(declaration.kind);\n\nconst resolvedSymbol =\n (checker: Checker) =>\n (symbol: TypeSymbol): TypeSymbol => {\n if (\n !pipe(\n declarationsOf(symbol),\n Arr.some((declaration) => isAliasDeclaration(declaration)),\n )\n ) {\n return symbol;\n }\n return checker.getAliasedSymbol(symbol);\n };\n\nconst symbolAt =\n (checker: Checker) =>\n (node: Node): Option.Option<TypeSymbol> =>\n Option.fromUndefinedOr(checker.getSymbolAtLocation(node)).pipe(\n Option.map(resolvedSymbol(checker)),\n );\n\nconst referencedSymbols =\n (checker: Checker) =>\n (node: Node): readonly TypeSymbol[] => {\n const identifiers = pipe(\n Arr.prepend(descendantsOf(node), node),\n Arr.filter((candidate) => isIdentifier(candidate)),\n );\n const symbols = pipe(\n checker.getSymbolAtLocation(identifiers),\n Arr.flatMap((symbol) => Option.toArray(Option.fromUndefinedOr(symbol))),\n Arr.map(resolvedSymbol(checker)),\n );\n return uniqueByReference(symbols);\n };\n\nconst lineOf = (node: Node): number => {\n const sourceFile = node.getSourceFile();\n return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + POSITION_BASE;\n};\n\nconst columnOf = (node: Node): number => {\n const sourceFile = node.getSourceFile();\n return (\n sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).character + POSITION_BASE\n );\n};\n\nconst contains =\n (outer: Node) =>\n (inner: Node): boolean =>\n outer.getSourceFile().fileName === inner.getSourceFile().fileName &&\n outer.getStart() <= inner.getStart() &&\n inner.getEnd() <= outer.getEnd();\n\nconst isTypeDeclaration = (\n declaration: Node,\n): declaration is TypeAliasDeclaration | InterfaceDeclaration | EnumDeclaration =>\n isTypeAliasDeclaration(declaration) ||\n isInterfaceDeclaration(declaration) ||\n isEnumDeclaration(declaration);\n\nconst isGenericDeclaration = (declaration: Node): boolean =>\n (isTypeAliasDeclaration(declaration) || isInterfaceDeclaration(declaration)) &&\n Arr.isReadonlyArrayNonEmpty(declaration.typeParameters ?? []);\n\nconst propertyNamesOf =\n (checker: Checker) =>\n (type: Type): readonly string[] =>\n pipe(\n checker.getPropertiesOfType(type),\n Arr.map((property) => property.name),\n );\n\nconst moduleSpecifierOf = (statement: Node): Option.Option<string> => {\n if (!isImportDeclaration(statement) && !isExportDeclaration(statement)) {\n return Option.none();\n }\n return Option.fromUndefinedOr(statement.moduleSpecifier).pipe(\n Option.filter((specifier) => isStringLiteral(specifier)),\n Option.map((specifier) => specifier.text),\n );\n};\n\nconst importedModulesOf = (sourceFile: SourceFile): readonly string[] =>\n pipe(\n sourceFile.statements,\n Arr.map((statement) => moduleSpecifierOf(statement)),\n Arr.getSomes,\n );\n\nexport {\n NODE_MODULES_SEGMENT,\n POSITION_BASE,\n childrenOf,\n columnOf,\n contains,\n declarationsOf,\n descendantsOf,\n importedModulesOf,\n isExternalDeclaration,\n isGenericDeclaration,\n isTypeDeclaration,\n lineOf,\n propertyNamesOf,\n referencedSymbols,\n resolvedSymbol,\n symbolAt,\n uniqueByReference,\n};\n","import { Array as Arr, Order, Schema, pipe } from \"effect\";\n\nimport type { Node } from \"./compiler.ts\";\nimport { columnOf, lineOf } from \"./syntax.ts\";\n\nconst RuleNameSchema = Schema.Literals([\n \"owner-annotation\",\n \"duplicate-value-set\",\n \"owner-value-set\",\n \"owner-extension\",\n \"owned-literal\",\n \"library-value-set\",\n \"library-shape\",\n \"unused-override\",\n \"removable-override\",\n \"hidden-unicode\",\n \"python-file\",\n \"python-command\",\n \"nested-docs\",\n]);\n\ntype RuleName = typeof RuleNameSchema.Type;\n\nconst ProblemSchema = Schema.Struct({\n file: Schema.String,\n line: Schema.Int,\n column: Schema.Int,\n rule: RuleNameSchema,\n message: Schema.String,\n});\n\ntype Problem = typeof ProblemSchema.Type;\n\nconst ProblemList = Schema.Array(ProblemSchema);\n\nconst CANDIDATE_LIMIT = 3;\n\ninterface Finding {\n readonly rule: RuleName;\n readonly message: string;\n}\n\nconst problemAt =\n ({ rule, message }: Finding) =>\n (node: Node): Problem => ({\n file: node.getSourceFile().fileName,\n line: lineOf(node),\n column: columnOf(node),\n rule,\n message,\n });\n\nconst problemOrder: Order.Order<Problem> = Order.combineAll([\n Order.mapInput(Order.String, (problem: Problem) => problem.file),\n Order.mapInput(Order.Number, (problem: Problem) => problem.line),\n Order.mapInput(Order.Number, (problem: Problem) => problem.column),\n Order.mapInput(Order.String, (problem: Problem) => problem.rule),\n Order.mapInput(Order.String, (problem: Problem) => problem.message),\n]);\n\nconst problemKey = (problem: Problem): string =>\n pipe(\n [problem.file, String(problem.line), String(problem.column), problem.rule, problem.message],\n Arr.join(\"\\0\"),\n );\n\nconst sortProblems = (problems: readonly Problem[]): readonly Problem[] =>\n Arr.sort(\n Arr.dedupeWith(problems, (left, right) => problemKey(left) === problemKey(right)),\n problemOrder,\n );\n\nconst describeFinding = (problem: Problem): string => `[${problem.rule}] ${problem.message}`;\n\nconst formatProblem =\n (relative: (file: string) => string) =>\n (problem: Problem): string =>\n `${relative(problem.file)}:${String(problem.line)}:${String(problem.column)} ${describeFinding(problem)}`;\n\nexport { CANDIDATE_LIMIT, ProblemList, describeFinding, formatProblem, problemAt, sortProblems };\nexport type { Finding, Problem, RuleName };\n","const JSON_FLAG = \"--json\";\n\nexport { JSON_FLAG };\n"],"mappings":"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"}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as isSatisfiesExpression, A as API, B as isCallExpression, C as MANIFEST_FILE$1, D as workspaceDependencies, E as readManifest, F as getLeadingCommentRanges, G as isInterfaceDeclaration, H as isElementAccessExpression, I as getNameOfDeclaration, J as isNewExpression, K as isModuleBlock, L as getTextOfJSDocComment, M as InternalSymbolName, N as SyntaxKind, O as RepositoryFiles, P as getJSDocTags, Q as isPropertyAssignment, R as isArrayLiteralExpression, S as uniqueByReference, T as dependencyEntries, U as isEnumDeclaration, V as isClassDeclaration, W as isIdentifier, X as isObjectLiteralExpression, Y as isNumericLiteral, Z as isPropertyAccessExpression, _ as lineOf, a as problemAt, at as isTypeLiteralNode, b as resolvedSymbol, c as childrenOf, ct as isVariableDeclaration, d as declarationsOf, et as isSignatureDeclaration, f as descendantsOf, g as isTypeDeclaration, h as isGenericDeclaration, i as formatProblem, it as isTypeAliasDeclaration, j as IndexKind, k as nodeRepositoryFiles, l as columnOf, lt as isVariableDeclarationList, m as isExternalDeclaration, n as ProblemList, nt as isSpreadElement, o as sortProblems, ot as isTypeReferenceNode, p as importedModulesOf, q as isModuleDeclaration, rt as isStringLiteral, s as NODE_MODULES_SEGMENT, st as isUnionTypeNode, tt as isSourceFile, u as contains, ut as isVariableStatement, v as propertyNamesOf, w as NODE_MODULES, x as symbolAt, y as referencedSymbols, z as isBinaryExpression } from "./cli-contract-CkYucdU4.mjs";
|
|
3
3
|
import { Array, Cause, Effect, FileSystem, Function, Layer, Number as Number$1, Option, Order, Path, Record, Schema, Stdio, Stream, String as String$1, pipe } from "effect";
|
|
4
4
|
import { layer } from "@effect/platform-node/NodeServices";
|
|
5
5
|
import { ChildProcess } from "effect/process";
|
|
@@ -322,6 +322,17 @@ const indexEntry = (cache) => (checker) => (entry) => Effect.sync(() => cache.in
|
|
|
322
322
|
"msch.library.shapes": index.shapes.length
|
|
323
323
|
})), Effect.map(({ index }) => index), Effect.withSpan("indexLibraryEntry", { attributes: { "msch.library.specifier": entry.specifier } }));
|
|
324
324
|
const libraryIndex = (cache) => (checker) => (entries) => pipe(entries, Effect.forEach(indexEntry(cache)(checker)), Effect.map(mergeIndexes), Effect.withSpan("libraryIndex"));
|
|
325
|
+
// src/derivation.ts
|
|
326
|
+
const contractOf = (node) => {
|
|
327
|
+
const { parent } = node;
|
|
328
|
+
if (isSatisfiesExpression(parent) && parent.expression === node) return [parent.type];
|
|
329
|
+
if (isVariableDeclaration(parent) && parent.initializer === node) return Option.toArray(Option.fromUndefinedOr(parent.type));
|
|
330
|
+
return [];
|
|
331
|
+
};
|
|
332
|
+
const derivedDeclarationsOf = (checker) => (node) => {
|
|
333
|
+
const scopes = Array.prepend(contractOf(node), node);
|
|
334
|
+
return new Set(pipe(scopes, Array.flatMap((scope) => referencedSymbols(checker)(scope)), Array.flatMap((symbol) => declarationsOf(symbol)), Array.map((declaration) => declarationKey(locationOf(declaration)))));
|
|
335
|
+
};
|
|
325
336
|
// src/shapes.ts
|
|
326
337
|
const MINIMUM_SHAPE_PROPERTIES = 3;
|
|
327
338
|
const EXACT = 0;
|
|
@@ -377,7 +388,6 @@ const localPropertiesOf = (checker) => (symbols) => pipe(Array.zip(symbols, chec
|
|
|
377
388
|
name: symbol.name,
|
|
378
389
|
type
|
|
379
390
|
})));
|
|
380
|
-
const derivedDeclarationsOf = (checker) => (node) => new Set(pipe(referencedSymbols(checker)(node), Array.flatMap((symbol) => declarationsOf(symbol)), Array.map((declaration) => declarationKey(locationOf(declaration)))));
|
|
381
391
|
const matchOrder = Order.mapInput(Order.Number, (match) => match.extra);
|
|
382
392
|
const shapeProblem = (site, matches) => {
|
|
383
393
|
const shown = pipe(Array.take(matches, 3), Array.map((match) => describeLibraryType(match.shape)), Array.join(", "));
|
|
@@ -390,14 +400,18 @@ const shapeProblem = (site, matches) => {
|
|
|
390
400
|
message: `This shape copies ${shown}. ${advice}`
|
|
391
401
|
})(site.node);
|
|
392
402
|
};
|
|
403
|
+
const candidatesOf = (context, open) => (site, locals) => {
|
|
404
|
+
const derived = derivedDeclarationsOf(context.checker)(site.node);
|
|
405
|
+
const named = pipe(context.shapes, Array.filter(hasEveryName(locals)));
|
|
406
|
+
if (pipe(named, Array.some((shape) => derived.has(declarationKey(shape.declaration))))) return [];
|
|
407
|
+
const candidates = pipe(named, Array.map((shape) => matchOf(context, open)(locals, shape)), Array.getSomes);
|
|
408
|
+
return Array.sort(candidates, matchOrder);
|
|
409
|
+
};
|
|
393
410
|
const shapeProblemOf = (context, open) => (site) => {
|
|
394
411
|
const symbols = context.checker.getPropertiesOfType(site.type);
|
|
395
412
|
if (symbols.length < MINIMUM_SHAPE_PROPERTIES) return Option.none();
|
|
396
413
|
const locals = localPropertiesOf(context.checker)(symbols);
|
|
397
|
-
|
|
398
|
-
const candidates = pipe(context.shapes, Array.filter(hasEveryName(locals)), Array.filter((shape) => !derived.has(declarationKey(shape.declaration))), Array.map((shape) => matchOf(context, open)(locals, shape)), Array.getSomes);
|
|
399
|
-
const matches = Array.sort(candidates, matchOrder);
|
|
400
|
-
return Option.liftPredicate(matches, Array.isReadonlyArrayNonEmpty).pipe(Option.map((found) => shapeProblem(site, found)));
|
|
414
|
+
return Option.liftPredicate(candidatesOf(context, open)(site, locals), Array.isReadonlyArrayNonEmpty).pipe(Option.map((found) => shapeProblem(site, found)));
|
|
401
415
|
};
|
|
402
416
|
const shapeProblemsIn = (context) => (sourceFile) => {
|
|
403
417
|
const sites = shapeSitesIn(context.checker)(sourceFile);
|
|
@@ -411,7 +425,10 @@ const namedValuesOf = (checker) => (symbol) => {
|
|
|
411
425
|
return finiteValuesOf(checker)(checker.getTypeOfSymbol(symbol));
|
|
412
426
|
};
|
|
413
427
|
const hasFiniteContext = (checker) => (node) => Option.fromUndefinedOr(checker.getContextualType(node)).pipe(Option.flatMap(finiteValuesOf(checker)), Option.isSome);
|
|
414
|
-
const isNamedValue = (element) =>
|
|
428
|
+
const isNamedValue = (element) => {
|
|
429
|
+
if (isSpreadElement(element)) return isNamedValue(element.expression);
|
|
430
|
+
return isIdentifier(element) || isPropertyAccessExpression(element) || isElementAccessExpression(element);
|
|
431
|
+
};
|
|
415
432
|
const namesEveryElement = (array) => pipe(array.elements, Array.every((element) => isNamedValue(element)));
|
|
416
433
|
const expressionValuesOf = (checker) => (node) => {
|
|
417
434
|
if (hasFiniteContext(checker)(node)) return Option.none();
|
|
@@ -665,7 +682,7 @@ const listConfigFiles = (directory) => Effect.gen(function* configFilesUnder() {
|
|
|
665
682
|
const parseConfig = (api) => (configPath) => Option.some(api.parseConfigFile(configPath)).pipe(Option.filter((parsed) => Array.isReadonlyArrayNonEmpty(parsed.fileNames)));
|
|
666
683
|
const nearestPackageDirectory = (directory, root) => Effect.gen(function* nearestPackage() {
|
|
667
684
|
const files = yield* RepositoryFiles;
|
|
668
|
-
const hasManifest = yield* files.exists(files.join(directory, MANIFEST_FILE));
|
|
685
|
+
const hasManifest = yield* files.exists(files.join(directory, MANIFEST_FILE$1));
|
|
669
686
|
const parent = files.dirname(directory);
|
|
670
687
|
if (hasManifest || directory === root || parent === directory) return directory;
|
|
671
688
|
return yield* nearestPackageDirectory(parent, root);
|
|
@@ -777,13 +794,93 @@ const checkRepository = Effect.fn("checkRepository")(function* checkRepository(r
|
|
|
777
794
|
...duplicateProblems(relative)(unowned)
|
|
778
795
|
]);
|
|
779
796
|
}, Effect.scoped);
|
|
780
|
-
// src/
|
|
781
|
-
const
|
|
782
|
-
|
|
797
|
+
// src/text-position.ts
|
|
798
|
+
const START$1 = 0;
|
|
799
|
+
const FIRST_COLUMN$1 = 1;
|
|
800
|
+
const positionOf = (source) => (index) => {
|
|
801
|
+
const before = pipe(source, String$1.slice(START$1, index), String$1.split("\n"));
|
|
802
|
+
return {
|
|
803
|
+
line: before.length,
|
|
804
|
+
column: pipe(Array.last(before), Option.match({
|
|
805
|
+
onNone: () => FIRST_COLUMN$1,
|
|
806
|
+
onSome: (last) => last.length + FIRST_COLUMN$1
|
|
807
|
+
}))
|
|
808
|
+
};
|
|
809
|
+
};
|
|
810
|
+
// src/hidden-unicode.ts
|
|
811
|
+
const HIDDEN = /[\u00AD\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]|[\u{E0000}-\u{E007F}]/gu;
|
|
812
|
+
const NUL = "\0";
|
|
813
|
+
const START = 0;
|
|
814
|
+
const HEX = 16;
|
|
815
|
+
const CODE_POINT_WIDTH = 4;
|
|
816
|
+
const READ_CONCURRENCY$2 = 64;
|
|
817
|
+
const RULE$1 = "hidden-unicode";
|
|
818
|
+
const hexOf = (character) => pipe(String$1.codePointAt(START)(character), Option.getOrElse(() => START), (code) => code.toString(HEX), String$1.toUpperCase, String$1.padStart(CODE_POINT_WIDTH, "0"));
|
|
819
|
+
const problemOf = (file, source) => (found) => {
|
|
820
|
+
const hex = hexOf(found[START]);
|
|
821
|
+
return {
|
|
822
|
+
file,
|
|
823
|
+
...positionOf(source)(found.index ?? START),
|
|
824
|
+
rule: RULE$1,
|
|
825
|
+
message: `U+${hex} is invisible in editors and reviews and can change what the text means. Remove it, or write it as the escape \\u${hex}.`
|
|
826
|
+
};
|
|
827
|
+
};
|
|
828
|
+
const problemsIn = (file, source) => {
|
|
829
|
+
if (String$1.includes(NUL)(source)) return [];
|
|
830
|
+
return pipe(Array.fromIterable(String$1.matchAll(HIDDEN)(source)), Array.map(problemOf(file, source)));
|
|
831
|
+
};
|
|
783
832
|
/** @internal */
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
yield*
|
|
833
|
+
const checkHiddenUnicode = Effect.fn("checkHiddenUnicode")(function* checkHiddenUnicode(root) {
|
|
834
|
+
const files = yield* RepositoryFiles;
|
|
835
|
+
const names = yield* files.listedFiles(root);
|
|
836
|
+
const found = yield* Effect.forEach(names, (name) => {
|
|
837
|
+
const file = files.join(root, name);
|
|
838
|
+
return files.readFile(file).pipe(Effect.map(Option.match({
|
|
839
|
+
onNone: () => [],
|
|
840
|
+
onSome: (source) => problemsIn(file, source)
|
|
841
|
+
})));
|
|
842
|
+
}, { concurrency: READ_CONCURRENCY$2 });
|
|
843
|
+
return Array.flatten(found);
|
|
844
|
+
});
|
|
845
|
+
// src/nested-docs.ts
|
|
846
|
+
const RULE = "nested-docs";
|
|
847
|
+
const SEPARATOR = "/";
|
|
848
|
+
const MANIFEST_FILE = "package.json";
|
|
849
|
+
const FIRST = 1;
|
|
850
|
+
const READ_CONCURRENCY$1 = 64;
|
|
851
|
+
const AGENT_DOCS = /^(?:AGENTS|CLAUDE)(?:\.[^/]+)?\.md$/iu;
|
|
852
|
+
const README = /^README(?:\.[^/]+)?$/iu;
|
|
853
|
+
const PublishedManifest = Schema.fromJsonString(Schema.Struct({ private: Schema.optional(Schema.Boolean) }));
|
|
854
|
+
const isNested = (name) => String$1.includes(SEPARATOR)(name);
|
|
855
|
+
const isPublishedPackage = Effect.fn("isPublishedPackage")(function* isPublishedPackage(root, directory) {
|
|
856
|
+
const files = yield* RepositoryFiles;
|
|
857
|
+
const text = yield* files.readFile(files.join(root, directory, MANIFEST_FILE));
|
|
858
|
+
return pipe(text, Option.flatMap(Schema.decodeUnknownOption(PublishedManifest)), Option.exists((manifest) => manifest.private !== true));
|
|
859
|
+
});
|
|
860
|
+
const problemFor = (file, message) => ({
|
|
861
|
+
file,
|
|
862
|
+
line: FIRST,
|
|
863
|
+
column: FIRST,
|
|
864
|
+
rule: RULE,
|
|
865
|
+
message
|
|
866
|
+
});
|
|
867
|
+
const AGENT_DOCS_MESSAGE = "belongs only at the repository root. Move what agents need into the root AGENTS.md and delete this file.";
|
|
868
|
+
const README_MESSAGE = "belongs only at the repository root or next to the package.json of a published package. Move its content into the root README.md and delete this file.";
|
|
869
|
+
const problemIn = Effect.fn("problemIn")(function* problemIn(root, name) {
|
|
870
|
+
const files = yield* RepositoryFiles;
|
|
871
|
+
const base = files.basename(name);
|
|
872
|
+
const file = files.join(root, name);
|
|
873
|
+
if (AGENT_DOCS.test(base)) return Option.some(problemFor(file, `${base} ${AGENT_DOCS_MESSAGE}`));
|
|
874
|
+
if (!README.test(base)) return Option.none();
|
|
875
|
+
if (yield* isPublishedPackage(root, files.dirname(name))) return Option.none();
|
|
876
|
+
return Option.some(problemFor(file, `${base} ${README_MESSAGE}`));
|
|
877
|
+
});
|
|
878
|
+
/** @internal */
|
|
879
|
+
const checkNestedDocs = Effect.fn("checkNestedDocs")(function* checkNestedDocs(root) {
|
|
880
|
+
const names = yield* (yield* RepositoryFiles).listedFiles(root);
|
|
881
|
+
const nested = pipe(names, Array.filter(isNested));
|
|
882
|
+
const found = yield* Effect.forEach(nested, (name) => problemIn(root, name), { concurrency: READ_CONCURRENCY$1 });
|
|
883
|
+
return Array.getSomes(found);
|
|
787
884
|
});
|
|
788
885
|
// src/override-findings.ts
|
|
789
886
|
const describeVersions = (versions) => Option.getOrElse(Option.liftPredicate(Array.join(versions, ","), (joined) => joined !== ""), () => "-");
|
|
@@ -966,18 +1063,18 @@ const withBaselineProbe = (manifest) => {
|
|
|
966
1063
|
};
|
|
967
1064
|
// src/overrides.ts
|
|
968
1065
|
const WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
969
|
-
const LOCKFILE = "pnpm-lock.yaml";
|
|
1066
|
+
const LOCKFILE$1 = "pnpm-lock.yaml";
|
|
970
1067
|
const RESOLUTION_FILES = [
|
|
971
1068
|
"*package.json",
|
|
972
1069
|
WORKSPACE_FILE,
|
|
973
|
-
LOCKFILE,
|
|
1070
|
+
LOCKFILE$1,
|
|
974
1071
|
".npmrc",
|
|
975
1072
|
"patches/*"
|
|
976
1073
|
];
|
|
977
1074
|
const RESOLUTION_CONCURRENCY = 4;
|
|
978
1075
|
const SUCCEEDED = 0;
|
|
979
|
-
const taggedError = Schema.TaggedError;
|
|
980
|
-
var OverrideResolutionFailed = class extends taggedError()("OverrideResolutionFailed", {
|
|
1076
|
+
const taggedError$1 = Schema.TaggedError;
|
|
1077
|
+
var OverrideResolutionFailed = class extends taggedError$1()("OverrideResolutionFailed", {
|
|
981
1078
|
override: Schema.String,
|
|
982
1079
|
output: Schema.String
|
|
983
1080
|
}) {};
|
|
@@ -1042,7 +1139,7 @@ const resolutionWith = Effect.fn("resolutionWith")(function* resolutionWith(work
|
|
|
1042
1139
|
yield* copyFiles(workspace, directory);
|
|
1043
1140
|
yield* fs.writeFileString(path.join(directory, WORKSPACE_FILE), manifest);
|
|
1044
1141
|
const output = yield* resolveIn(directory, override);
|
|
1045
|
-
const lockfile = yield* fs.readFileString(path.join(directory, LOCKFILE));
|
|
1142
|
+
const lockfile = yield* fs.readFileString(path.join(directory, LOCKFILE$1));
|
|
1046
1143
|
yield* Effect.annotateCurrentSpan({ "msch.override": override });
|
|
1047
1144
|
const advisories = yield* auditIn(directory);
|
|
1048
1145
|
return resolutionOf({
|
|
@@ -1079,6 +1176,84 @@ const checkOverrides = Effect.fn("checkOverrides")(function* checkOverrides(root
|
|
|
1079
1176
|
if (Option.isNone(manifest) || !Array.isReadonlyArrayNonEmpty(keys)) return [];
|
|
1080
1177
|
return yield* problemsFor(root, manifest.value, keys);
|
|
1081
1178
|
});
|
|
1179
|
+
// src/python.ts
|
|
1180
|
+
const PYTHON_FILE = /(?:\.(?:py|pyi|pyw|pyx|ipynb)|(?:^|\/)(?:pyproject\.toml|uv\.lock|uv\.toml|poetry\.lock|Pipfile|Pipfile\.lock|setup\.cfg|tox\.ini|pytest\.ini|\.python-version|requirements[^/]*\.txt))$/u;
|
|
1181
|
+
const SCANNED_FILE = /(?:\.(?:[cm]?[jt]sx?|jsonc?|toml|ya?ml|sh|bash|zsh|fish|rs|cs|go|mk)|(?:^|\/)(?:Dockerfile[^/]*|Makefile|justfile|\.tool-versions|[^./][^./]*))$/u;
|
|
1182
|
+
const LOCKFILE = /(?:^|\/)(?:[^/]*\.lock|[^/]*-lock\.(?:json|ya?ml))$/u;
|
|
1183
|
+
const TOOL_VERSIONS = /(?:^|\/)(?:\.?mise(?:\.[^/]+)?\.toml|\.tool-versions)$/u;
|
|
1184
|
+
const COMMAND_PATTERNS = [
|
|
1185
|
+
/(?<![\w./@-])python[\d.]*[ \t]+(?:[-"'$./]|\S+\.py\b)/gu,
|
|
1186
|
+
/(?<![\w./@-])pip3?[ \t]+(?:install|uninstall|download|freeze|wheel|compile|sync)\b/gu,
|
|
1187
|
+
/(?<![\w./@-])pipx[ \t]+(?:run|install|inject|upgrade)\b/gu,
|
|
1188
|
+
/(?<![\w./@-])uvx[ \t]+[^\s=:,;)|&]/gu,
|
|
1189
|
+
/(?<![\w./@-])uv[ \t]+(?:run|sync|lock|pip|tool|venv|add|remove|python|export|build|publish|init|tree|cache|self)\b/gu,
|
|
1190
|
+
/(?<![\w./@-])poetry[ \t]+(?:run|install|add|lock|build|publish|shell|export)\b/gu,
|
|
1191
|
+
/(?:\bcommand["']?\s*[:=]\s*|(?:\bspawn\w*|\bexec\w*|\bexeca|Command::new|ChildProcess\.make|\$)\(\s*)["'`](?:python[\d.]*|pip3?|pipx|uvx?|poetry)["'`]/gu,
|
|
1192
|
+
/\buses:\s*["']?(?:actions\/setup-python|astral-sh\/setup-uv|astral-sh\/ruff-action|pypa\/[\w.-]+|snok\/install-poetry|abatilo\/actions-poetry|conda-incubator\/setup-miniconda|mamba-org\/setup-micromamba|pdm-project\/setup-pdm)\b/gu
|
|
1193
|
+
];
|
|
1194
|
+
const TOOL_PATTERNS = [/^[ \t]*["']?(?:python|uv|pipx|poetry|ruff|pdm|hatch|pipx:[^"'\s=]+)["']?[ \t]*(?:=|[ \t]+\S)/gmu];
|
|
1195
|
+
const READ_CONCURRENCY = 64;
|
|
1196
|
+
const FIRST_LINE = 1;
|
|
1197
|
+
const FIRST_COLUMN = 1;
|
|
1198
|
+
const WHOLE_MATCH = 0;
|
|
1199
|
+
const FILE_RULE = "python-file";
|
|
1200
|
+
const COMMAND_RULE = "python-command";
|
|
1201
|
+
const fileProblem = ({ file }) => ({
|
|
1202
|
+
file,
|
|
1203
|
+
line: FIRST_LINE,
|
|
1204
|
+
column: FIRST_COLUMN,
|
|
1205
|
+
rule: FILE_RULE,
|
|
1206
|
+
message: "Python is not used in this repository. Replace this file with TypeScript or Rust."
|
|
1207
|
+
});
|
|
1208
|
+
const patternsFor = (name) => {
|
|
1209
|
+
if (TOOL_VERSIONS.test(name)) return [...COMMAND_PATTERNS, ...TOOL_PATTERNS];
|
|
1210
|
+
return COMMAND_PATTERNS;
|
|
1211
|
+
};
|
|
1212
|
+
const commandProblem = ({ file }, source) => (found) => ({
|
|
1213
|
+
file,
|
|
1214
|
+
...positionOf(source)(found.index ?? WHOLE_MATCH),
|
|
1215
|
+
rule: COMMAND_RULE,
|
|
1216
|
+
message: `Python is not used in this repository. Remove \`${String$1.trim(found[WHOLE_MATCH])}\` and do the work in TypeScript or Rust.`
|
|
1217
|
+
});
|
|
1218
|
+
const commandProblems = (entry, source) => pipe(patternsFor(entry.name), Array.flatMap((pattern) => Array.fromIterable(String$1.matchAll(pattern)(source))), Array.map(commandProblem(entry, source)));
|
|
1219
|
+
const isPython = ({ name }) => PYTHON_FILE.test(name);
|
|
1220
|
+
const isScanned = (entry) => !isPython(entry) && SCANNED_FILE.test(entry.name) && !LOCKFILE.test(entry.name);
|
|
1221
|
+
/** @internal */
|
|
1222
|
+
const checkPython = Effect.fn("checkPython")(function* checkPython(root) {
|
|
1223
|
+
const files = yield* RepositoryFiles;
|
|
1224
|
+
const names = yield* files.listedFiles(root);
|
|
1225
|
+
const listed = pipe(names, Array.map((name) => ({
|
|
1226
|
+
name,
|
|
1227
|
+
file: files.join(root, name)
|
|
1228
|
+
})));
|
|
1229
|
+
const problemsIn = (entry) => files.readFile(entry.file).pipe(Effect.map(Option.match({
|
|
1230
|
+
onNone: () => [],
|
|
1231
|
+
onSome: (source) => commandProblems(entry, source)
|
|
1232
|
+
})));
|
|
1233
|
+
const scanned = yield* Effect.forEach(pipe(listed, Array.filter(isScanned)), problemsIn, { concurrency: READ_CONCURRENCY });
|
|
1234
|
+
const present = yield* Effect.filter(pipe(listed, Array.filter(isPython)), ({ file }) => files.exists(file));
|
|
1235
|
+
return [...pipe(present, Array.map(fileProblem)), ...Array.flatten(scanned)];
|
|
1236
|
+
});
|
|
1237
|
+
// src/all-checks.ts
|
|
1238
|
+
/** @internal */
|
|
1239
|
+
const checkAll = Effect.fn("checkAll")(function* checkAll(root) {
|
|
1240
|
+
const found = yield* Effect.all([
|
|
1241
|
+
checkRepository(root),
|
|
1242
|
+
checkOverrides(root),
|
|
1243
|
+
checkHiddenUnicode(root),
|
|
1244
|
+
checkPython(root),
|
|
1245
|
+
checkNestedDocs(root)
|
|
1246
|
+
], { concurrency: "unbounded" });
|
|
1247
|
+
return Array.flatten(found);
|
|
1248
|
+
});
|
|
1249
|
+
// src/output.ts
|
|
1250
|
+
const taggedError = Schema.TaggedError;
|
|
1251
|
+
var ProblemsFound = class extends taggedError()("ProblemsFound", { count: Schema.Finite }) {};
|
|
1252
|
+
/** @internal */
|
|
1253
|
+
const writeStdout = Effect.fn("writeStdout")(function* writeStdout(text) {
|
|
1254
|
+
const stdio = yield* Stdio.Stdio;
|
|
1255
|
+
yield* Stream.make(text).pipe(Stream.run(stdio.stdout()), Effect.orDie);
|
|
1256
|
+
});
|
|
1082
1257
|
// src/runtime.ts
|
|
1083
1258
|
const reportFailure = (cause) => Effect.gen(function* report() {
|
|
1084
1259
|
const stdio = yield* Stdio.Stdio;
|
|
@@ -1105,8 +1280,8 @@ runCli(Effect.gen(function* msch() {
|
|
|
1105
1280
|
const files = yield* RepositoryFiles;
|
|
1106
1281
|
const args = yield* stdio.args;
|
|
1107
1282
|
const root = files.resolve(".");
|
|
1108
|
-
const found = yield*
|
|
1109
|
-
const problems = sortProblems(
|
|
1283
|
+
const found = yield* checkAll(root);
|
|
1284
|
+
const problems = sortProblems(found);
|
|
1110
1285
|
if (args.includes("--json")) {
|
|
1111
1286
|
const text = yield* Schema.encodeEffect(Schema.fromJsonString(ProblemList))(problems);
|
|
1112
1287
|
return yield* writeStdout(text);
|