@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 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 generatedFiles = (root) => Effect.gen(function* listGenerated() {
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), Array.map((file) => path.join(root, file)));
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 { isSignatureDeclaration 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, isUnionTypeNode as at, resolvedSymbol as b, childrenOf as c, isVariableStatement as ct, declarationsOf as d, isSourceFile as et, descendantsOf as f, isTypeDeclaration as g, isGenericDeclaration as h, formatProblem as i, isTypeReferenceNode as it, IndexKind as j, nodeRepositoryFiles as k, columnOf as l, isExternalDeclaration as m, ProblemList as n, isTypeAliasDeclaration as nt, sortProblems as o, isVariableDeclaration as ot, importedModulesOf as p, isModuleDeclaration as q, describeFinding as r, isTypeLiteralNode as rt, NODE_MODULES_SEGMENT as s, isVariableDeclarationList as st, JSON_FLAG as t, isStringLiteral as tt, contains as u, propertyNamesOf as v, NODE_MODULES as w, symbolAt as x, referencedSymbols as y, isBinaryExpression as z };
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-Wo9XK0jk.mjs.map
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 isSignatureDeclaration, A as API, B as isCallExpression, C as MANIFEST_FILE, 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 isUnionTypeNode, b as resolvedSymbol, c as childrenOf, ct as isVariableStatement, d as declarationsOf, et as isSourceFile, f as descendantsOf, g as isTypeDeclaration, h as isGenericDeclaration, i as formatProblem, it as isTypeReferenceNode, j as IndexKind, k as nodeRepositoryFiles, l as columnOf, m as isExternalDeclaration, n as ProblemList, nt as isTypeAliasDeclaration, o as sortProblems, ot as isVariableDeclaration, p as importedModulesOf, q as isModuleDeclaration, rt as isTypeLiteralNode, s as NODE_MODULES_SEGMENT, st as isVariableDeclarationList, tt as isStringLiteral, u as contains, v as propertyNamesOf, w as NODE_MODULES, x as symbolAt, y as referencedSymbols, z as isBinaryExpression } from "./cli-contract-Wo9XK0jk.mjs";
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
- const derived = derivedDeclarationsOf(context.checker)(site.node);
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) => isIdentifier(element) || isPropertyAccessExpression(element) || isElementAccessExpression(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/output.ts
781
- const taggedError$1 = Schema.TaggedError;
782
- var ProblemsFound = class extends taggedError$1()("ProblemsFound", { count: Schema.Finite }) {};
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 writeStdout = Effect.fn("writeStdout")(function* writeStdout(text) {
785
- const stdio = yield* Stdio.Stdio;
786
- yield* Stream.make(text).pipe(Stream.run(stdio.stdout()), Effect.orDie);
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* Effect.all([checkRepository(root), checkOverrides(root)], { concurrency: "unbounded" });
1109
- const problems = sortProblems(Array.flatten(found));
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);