@r_masseater/msch 0.1.0 → 0.2.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,7 @@ oxlint のバージョンは、@effect/tsgo が対応するものに合わせる
38
38
 
39
39
  ## msch の検査
40
40
 
41
- リポジトリのルートで `msch` を実行すると、値の集合と形の重複、overrides の要不要、テストのためだけのコードを調べる。
41
+ リポジトリのルートで `msch` を実行すると、値の集合と形の重複、overrides の要不要、テストのためだけのコード、見えない文字、Python の使用を調べる。
42
+ 見えない文字は、ゼロ幅の文字・書字方向を変える制御文字・タグ文字のように、エディタやレビューで見えずに意味を変えうる文字で、`git ls-files` に載るテキストのファイルから探す。ファイル先頭の BOM と、NUL を含むバイナリは対象にしない。
43
+ Python は使わない方針なので、`.py`・`pyproject.toml`・`uv.lock` などのファイルを指摘する。スクリプト・CI・設定の中で `python3`・`pip`・`uv`・`poetry` を呼ぶ箇所と、`actions/setup-python` などの action、`mise.toml` に書いた Python の道具も指摘する。
42
44
  指摘があれば終了コードは 1 になる。`--json` を付けると結果を JSON で出す。
@@ -6,25 +6,30 @@ 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,10 @@ 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"
234
242
  ]);
235
243
  const ProblemSchema = Schema.Struct({
236
244
  file: Schema.String,
@@ -268,4 +276,4 @@ const formatProblem = (relative) => (problem) => `${relative(problem.file)}:${St
268
276
  const JSON_FLAG = "--json";
269
277
  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 };
270
278
 
271
- //# sourceMappingURL=cli-contract-Wo9XK0jk.mjs.map
279
+ //# sourceMappingURL=cli-contract-BRpk_UKr.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-contract-BRpk_UKr.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]);\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 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-BRpk_UKr.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";
@@ -777,6 +777,54 @@ const checkRepository = Effect.fn("checkRepository")(function* checkRepository(r
777
777
  ...duplicateProblems(relative)(unowned)
778
778
  ]);
779
779
  }, Effect.scoped);
780
+ // src/text-position.ts
781
+ const START$1 = 0;
782
+ const FIRST_COLUMN$1 = 1;
783
+ const positionOf = (source) => (index) => {
784
+ const before = pipe(source, String$1.slice(START$1, index), String$1.split("\n"));
785
+ return {
786
+ line: before.length,
787
+ column: pipe(Array.last(before), Option.match({
788
+ onNone: () => FIRST_COLUMN$1,
789
+ onSome: (last) => last.length + FIRST_COLUMN$1
790
+ }))
791
+ };
792
+ };
793
+ // src/hidden-unicode.ts
794
+ const HIDDEN = /[\u00AD\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]|[\u{E0000}-\u{E007F}]/gu;
795
+ const NUL = "\0";
796
+ const START = 0;
797
+ const HEX = 16;
798
+ const CODE_POINT_WIDTH = 4;
799
+ const READ_CONCURRENCY$1 = 64;
800
+ const RULE = "hidden-unicode";
801
+ 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"));
802
+ const problemOf = (file, source) => (found) => {
803
+ const hex = hexOf(found[START]);
804
+ return {
805
+ file,
806
+ ...positionOf(source)(found.index ?? START),
807
+ rule: RULE,
808
+ 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}.`
809
+ };
810
+ };
811
+ const problemsIn = (file, source) => {
812
+ if (String$1.includes(NUL)(source)) return [];
813
+ return pipe(Array.fromIterable(String$1.matchAll(HIDDEN)(source)), Array.map(problemOf(file, source)));
814
+ };
815
+ /** @internal */
816
+ const checkHiddenUnicode = Effect.fn("checkHiddenUnicode")(function* checkHiddenUnicode(root) {
817
+ const files = yield* RepositoryFiles;
818
+ const names = yield* files.listedFiles(root);
819
+ const found = yield* Effect.forEach(names, (name) => {
820
+ const file = files.join(root, name);
821
+ return files.readFile(file).pipe(Effect.map(Option.match({
822
+ onNone: () => [],
823
+ onSome: (source) => problemsIn(file, source)
824
+ })));
825
+ }, { concurrency: READ_CONCURRENCY$1 });
826
+ return Array.flatten(found);
827
+ });
780
828
  // src/output.ts
781
829
  const taggedError$1 = Schema.TaggedError;
782
830
  var ProblemsFound = class extends taggedError$1()("ProblemsFound", { count: Schema.Finite }) {};
@@ -966,11 +1014,11 @@ const withBaselineProbe = (manifest) => {
966
1014
  };
967
1015
  // src/overrides.ts
968
1016
  const WORKSPACE_FILE = "pnpm-workspace.yaml";
969
- const LOCKFILE = "pnpm-lock.yaml";
1017
+ const LOCKFILE$1 = "pnpm-lock.yaml";
970
1018
  const RESOLUTION_FILES = [
971
1019
  "*package.json",
972
1020
  WORKSPACE_FILE,
973
- LOCKFILE,
1021
+ LOCKFILE$1,
974
1022
  ".npmrc",
975
1023
  "patches/*"
976
1024
  ];
@@ -1042,7 +1090,7 @@ const resolutionWith = Effect.fn("resolutionWith")(function* resolutionWith(work
1042
1090
  yield* copyFiles(workspace, directory);
1043
1091
  yield* fs.writeFileString(path.join(directory, WORKSPACE_FILE), manifest);
1044
1092
  const output = yield* resolveIn(directory, override);
1045
- const lockfile = yield* fs.readFileString(path.join(directory, LOCKFILE));
1093
+ const lockfile = yield* fs.readFileString(path.join(directory, LOCKFILE$1));
1046
1094
  yield* Effect.annotateCurrentSpan({ "msch.override": override });
1047
1095
  const advisories = yield* auditIn(directory);
1048
1096
  return resolutionOf({
@@ -1079,6 +1127,64 @@ const checkOverrides = Effect.fn("checkOverrides")(function* checkOverrides(root
1079
1127
  if (Option.isNone(manifest) || !Array.isReadonlyArrayNonEmpty(keys)) return [];
1080
1128
  return yield* problemsFor(root, manifest.value, keys);
1081
1129
  });
1130
+ // src/python.ts
1131
+ 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;
1132
+ const SCANNED_FILE = /(?:\.(?:[cm]?[jt]sx?|jsonc?|toml|ya?ml|sh|bash|zsh|fish|rs|cs|go|mk)|(?:^|\/)(?:Dockerfile[^/]*|Makefile|justfile|\.tool-versions|[^./][^./]*))$/u;
1133
+ const LOCKFILE = /(?:^|\/)(?:[^/]*\.lock|[^/]*-lock\.(?:json|ya?ml))$/u;
1134
+ const TOOL_VERSIONS = /(?:^|\/)(?:\.?mise(?:\.[^/]+)?\.toml|\.tool-versions)$/u;
1135
+ const COMMAND_PATTERNS = [
1136
+ /(?<![\w./@-])python[\d.]*[ \t]+(?:[-"'$./]|\S+\.py\b)/gu,
1137
+ /(?<![\w./@-])pip3?[ \t]+(?:install|uninstall|download|freeze|wheel|compile|sync)\b/gu,
1138
+ /(?<![\w./@-])pipx[ \t]+(?:run|install|inject|upgrade)\b/gu,
1139
+ /(?<![\w./@-])uvx[ \t]+[^\s=:,;)|&]/gu,
1140
+ /(?<![\w./@-])uv[ \t]+(?:run|sync|lock|pip|tool|venv|add|remove|python|export|build|publish|init|tree|cache|self)\b/gu,
1141
+ /(?<![\w./@-])poetry[ \t]+(?:run|install|add|lock|build|publish|shell|export)\b/gu,
1142
+ /(?:\bcommand["']?\s*[:=]\s*|(?:\bspawn\w*|\bexec\w*|\bexeca|Command::new|ChildProcess\.make|\$)\(\s*)["'`](?:python[\d.]*|pip3?|pipx|uvx?|poetry)["'`]/gu,
1143
+ /\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
1144
+ ];
1145
+ const TOOL_PATTERNS = [/^[ \t]*["']?(?:python|uv|pipx|poetry|ruff|pdm|hatch|pipx:[^"'\s=]+)["']?[ \t]*(?:=|[ \t]+\S)/gmu];
1146
+ const READ_CONCURRENCY = 64;
1147
+ const FIRST_LINE = 1;
1148
+ const FIRST_COLUMN = 1;
1149
+ const WHOLE_MATCH = 0;
1150
+ const FILE_RULE = "python-file";
1151
+ const COMMAND_RULE = "python-command";
1152
+ const fileProblem = ({ file }) => ({
1153
+ file,
1154
+ line: FIRST_LINE,
1155
+ column: FIRST_COLUMN,
1156
+ rule: FILE_RULE,
1157
+ message: "Python is not used in this repository. Replace this file with TypeScript or Rust."
1158
+ });
1159
+ const patternsFor = (name) => {
1160
+ if (TOOL_VERSIONS.test(name)) return [...COMMAND_PATTERNS, ...TOOL_PATTERNS];
1161
+ return COMMAND_PATTERNS;
1162
+ };
1163
+ const commandProblem = ({ file }, source) => (found) => ({
1164
+ file,
1165
+ ...positionOf(source)(found.index ?? WHOLE_MATCH),
1166
+ rule: COMMAND_RULE,
1167
+ message: `Python is not used in this repository. Remove \`${String$1.trim(found[WHOLE_MATCH])}\` and do the work in TypeScript or Rust.`
1168
+ });
1169
+ const commandProblems = (entry, source) => pipe(patternsFor(entry.name), Array.flatMap((pattern) => Array.fromIterable(String$1.matchAll(pattern)(source))), Array.map(commandProblem(entry, source)));
1170
+ const isPython = ({ name }) => PYTHON_FILE.test(name);
1171
+ const isScanned = (entry) => !isPython(entry) && SCANNED_FILE.test(entry.name) && !LOCKFILE.test(entry.name);
1172
+ /** @internal */
1173
+ const checkPython = Effect.fn("checkPython")(function* checkPython(root) {
1174
+ const files = yield* RepositoryFiles;
1175
+ const names = yield* files.listedFiles(root);
1176
+ const listed = pipe(names, Array.map((name) => ({
1177
+ name,
1178
+ file: files.join(root, name)
1179
+ })));
1180
+ const problemsIn = (entry) => files.readFile(entry.file).pipe(Effect.map(Option.match({
1181
+ onNone: () => [],
1182
+ onSome: (source) => commandProblems(entry, source)
1183
+ })));
1184
+ const scanned = yield* Effect.forEach(pipe(listed, Array.filter(isScanned)), problemsIn, { concurrency: READ_CONCURRENCY });
1185
+ const present = yield* Effect.filter(pipe(listed, Array.filter(isPython)), ({ file }) => files.exists(file));
1186
+ return [...pipe(present, Array.map(fileProblem)), ...Array.flatten(scanned)];
1187
+ });
1082
1188
  // src/runtime.ts
1083
1189
  const reportFailure = (cause) => Effect.gen(function* report() {
1084
1190
  const stdio = yield* Stdio.Stdio;
@@ -1105,7 +1211,12 @@ runCli(Effect.gen(function* msch() {
1105
1211
  const files = yield* RepositoryFiles;
1106
1212
  const args = yield* stdio.args;
1107
1213
  const root = files.resolve(".");
1108
- const found = yield* Effect.all([checkRepository(root), checkOverrides(root)], { concurrency: "unbounded" });
1214
+ const found = yield* Effect.all([
1215
+ checkRepository(root),
1216
+ checkOverrides(root),
1217
+ checkHiddenUnicode(root),
1218
+ checkPython(root)
1219
+ ], { concurrency: "unbounded" });
1109
1220
  const problems = sortProblems(Array.flatten(found));
1110
1221
  if (args.includes("--json")) {
1111
1222
  const text = yield* Schema.encodeEffect(Schema.fromJsonString(ProblemList))(problems);