yukigo 0.2.0 → 0.2.3

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/analyzer/index.d.ts +7 -0
  3. package/dist/analyzer/inspections/generic/generic.d.ts +1 -2
  4. package/dist/analyzer/inspections/generic/generic.js +2 -5
  5. package/dist/src/analyzer/GraphBuilder.d.ts +30 -0
  6. package/dist/src/analyzer/GraphBuilder.js +100 -0
  7. package/dist/src/analyzer/index.d.ts +59 -0
  8. package/dist/src/analyzer/index.js +152 -0
  9. package/dist/src/analyzer/inspections/functional/functional.d.ts +44 -0
  10. package/dist/src/analyzer/inspections/functional/functional.js +149 -0
  11. package/dist/src/analyzer/inspections/functional/smells.d.ts +16 -0
  12. package/dist/src/analyzer/inspections/functional/smells.js +98 -0
  13. package/dist/src/analyzer/inspections/generic/generic.d.ts +178 -0
  14. package/dist/src/analyzer/inspections/generic/generic.js +604 -0
  15. package/dist/src/analyzer/inspections/generic/smells.d.ts +61 -0
  16. package/dist/src/analyzer/inspections/generic/smells.js +349 -0
  17. package/dist/src/analyzer/inspections/imperative/imperative.d.ts +35 -0
  18. package/dist/src/analyzer/inspections/imperative/imperative.js +109 -0
  19. package/dist/src/analyzer/inspections/imperative/smells.d.ts +16 -0
  20. package/dist/src/analyzer/inspections/imperative/smells.js +58 -0
  21. package/dist/src/analyzer/inspections/logic/logic.d.ts +32 -0
  22. package/dist/src/analyzer/inspections/logic/logic.js +96 -0
  23. package/dist/src/analyzer/inspections/logic/smells.d.ts +15 -0
  24. package/dist/src/analyzer/inspections/logic/smells.js +60 -0
  25. package/dist/src/analyzer/inspections/object/object.d.ts +90 -0
  26. package/dist/src/analyzer/inspections/object/object.js +321 -0
  27. package/dist/src/analyzer/inspections/object/smells.d.ts +30 -0
  28. package/dist/src/analyzer/inspections/object/smells.js +135 -0
  29. package/dist/src/analyzer/utils.d.ts +30 -0
  30. package/dist/src/analyzer/utils.js +78 -0
  31. package/dist/src/index.d.ts +4 -0
  32. package/dist/src/index.js +4 -0
  33. package/dist/src/interpreter/components/EnvBuilder.d.ts +21 -0
  34. package/dist/src/interpreter/components/EnvBuilder.js +155 -0
  35. package/dist/src/interpreter/components/Operations.d.ts +14 -0
  36. package/dist/src/interpreter/components/Operations.js +84 -0
  37. package/dist/src/interpreter/components/PatternMatcher.d.ts +73 -0
  38. package/dist/src/interpreter/components/PatternMatcher.js +358 -0
  39. package/dist/src/interpreter/components/RuntimeContext.d.ts +35 -0
  40. package/dist/src/interpreter/components/RuntimeContext.js +93 -0
  41. package/dist/src/interpreter/components/TestRunner.d.ts +19 -0
  42. package/dist/src/interpreter/components/TestRunner.js +110 -0
  43. package/dist/src/interpreter/components/Visitor.d.ts +71 -0
  44. package/dist/src/interpreter/components/Visitor.js +638 -0
  45. package/dist/src/interpreter/components/logic/LogicEngine.d.ts +29 -0
  46. package/dist/src/interpreter/components/logic/LogicEngine.js +259 -0
  47. package/dist/src/interpreter/components/logic/LogicResolver.d.ts +53 -0
  48. package/dist/src/interpreter/components/logic/LogicResolver.js +484 -0
  49. package/dist/src/interpreter/components/logic/LogicTranslator.d.ts +14 -0
  50. package/dist/src/interpreter/components/logic/LogicTranslator.js +99 -0
  51. package/dist/src/interpreter/components/runtimes/FunctionRuntime.d.ts +12 -0
  52. package/dist/src/interpreter/components/runtimes/FunctionRuntime.js +149 -0
  53. package/dist/src/interpreter/components/runtimes/LazyRuntime.d.ts +19 -0
  54. package/dist/src/interpreter/components/runtimes/LazyRuntime.js +269 -0
  55. package/dist/src/interpreter/components/runtimes/ObjectRuntime.d.ts +35 -0
  56. package/dist/src/interpreter/components/runtimes/ObjectRuntime.js +126 -0
  57. package/dist/src/interpreter/errors.d.ts +19 -0
  58. package/dist/src/interpreter/errors.js +36 -0
  59. package/dist/src/interpreter/index.d.ts +24 -0
  60. package/dist/src/interpreter/index.js +41 -0
  61. package/dist/src/interpreter/trampoline.d.ts +17 -0
  62. package/dist/src/interpreter/trampoline.js +38 -0
  63. package/dist/src/interpreter/utils.d.ts +11 -0
  64. package/dist/src/interpreter/utils.js +65 -0
  65. package/dist/src/tester/index.d.ts +25 -0
  66. package/dist/src/tester/index.js +113 -0
  67. package/dist/src/utils/helpers.d.ts +13 -0
  68. package/dist/src/utils/helpers.js +52 -0
  69. package/dist/utils/helpers.d.ts +5 -1
  70. package/dist/utils/helpers.js +82 -6
  71. package/package.json +4 -2
  72. package/src/analyzer/index.ts +9 -0
  73. package/src/analyzer/inspections/generic/generic.ts +2 -6
  74. package/src/utils/helpers.ts +112 -10
  75. package/tests/analyzer/helpers.spec.ts +16 -10
  76. package/tests/tester/Tester.spec.ts +0 -1
@@ -0,0 +1,41 @@
1
+ import { InterpreterVisitor } from "./components/Visitor.js";
2
+ import { EnvBuilderVisitor } from "./components/EnvBuilder.js";
3
+ import { InterpreterError } from "./errors.js";
4
+ import { idContinuation, trampoline } from "./trampoline.js";
5
+ import { RuntimeContext, } from "./components/RuntimeContext.js";
6
+ /**
7
+ * The Interpreter class is responsible for evaluating the Abstract Syntax Tree (AST)
8
+ * generated by the parsers.
9
+ *
10
+ * It manages the global execution environment and delegates the actual evaluation
11
+ * of Expression nodes to a dedicated visitor.
12
+ */
13
+ export class Interpreter {
14
+ context;
15
+ /**
16
+ * @param ast The Abstract Syntax Tree (AST) of the program to be interpreted.
17
+ */
18
+ constructor(ast, config = {}) {
19
+ this.context = new RuntimeContext(config);
20
+ const builder = new EnvBuilderVisitor(this.context);
21
+ builder.build(ast);
22
+ }
23
+ /**
24
+ * Evaluates a single Expression node within the context of the global environment.
25
+ *
26
+ * @param expr The root Expression node to be evaluated.
27
+ * @returns The resulting primitive value (number, string, boolean, etc.) after evaluation.
28
+ */
29
+ evaluate(expr) {
30
+ try {
31
+ const visitor = new InterpreterVisitor(this.context);
32
+ const evaluatedCPS = expr.accept(visitor);
33
+ return trampoline(evaluatedCPS(idContinuation));
34
+ }
35
+ catch (error) {
36
+ if (error instanceof InterpreterError)
37
+ console.log(error.formatStack());
38
+ throw error;
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,17 @@
1
+ import { PrimitiveValue } from "yukigo-ast";
2
+ export type CallableThunk<T> = () => Thunk<T>;
3
+ export type Thunk<T> = T | CallableThunk<T>;
4
+ export type Continuation<T, R = any> = (value: T) => Thunk<R>;
5
+ export declare const idContinuation: Continuation<PrimitiveValue>;
6
+ export declare function trampoline<T>(thunk: Thunk<T>): T;
7
+ /**
8
+ * A specialized Thunk type for our CPS visitor.
9
+ * This is a function that, when called, takes the continuation for the current node's evaluation
10
+ * and returns the actual Thunk chain for that node.
11
+ * This effectively injects the continuation *after* the `visit` method has returned.
12
+ */
13
+ export type CPSThunk<T, R = any> = (k: Continuation<T, R>) => Thunk<R>;
14
+ export declare function makeCPSThunk<T>(fn: (k: Continuation<T>) => Thunk<T>): CPSThunk<T>;
15
+ export declare function bindCPS<A, B, R>(cpsA: CPSThunk<A, R>, f: (valueA: A) => CPSThunk<B, R>): CPSThunk<B, R>;
16
+ export declare function valueToCPS<T, R>(value: T): CPSThunk<T, R>;
17
+ export declare function thunkToCPS<T>(thunk: Thunk<T>): CPSThunk<T>;
@@ -0,0 +1,38 @@
1
+ const isCallableThunk = (thunk) => typeof thunk === "function";
2
+ // The identity continuation: simply returns the value as a resolved Thunk.
3
+ // This is used for the very last step of a computation or when a value is final.
4
+ export const idContinuation = (value) => value;
5
+ // The trampoline function executes a chain of Thunks until a final non-function value is reached.
6
+ export function trampoline(thunk) {
7
+ let result = thunk;
8
+ while (isCallableThunk(result))
9
+ result = result();
10
+ return result;
11
+ }
12
+ // Helper to create a CPSThunk for visitor methods.
13
+ // Visitor methods will call this, providing a function that knows how to use 'k'.
14
+ export function makeCPSThunk(fn) {
15
+ return fn;
16
+ }
17
+ // Helper to compose asynchronous/CPS operations.
18
+ // `cpsA` is a CPSThunk that eventually produces a value A.
19
+ // `f` is a function that takes A and returns a CPSThunk that eventually produces B.
20
+ export function bindCPS(cpsA, f) {
21
+ return (k) => {
22
+ return () => cpsA((valueA) => {
23
+ // f(valueA) returns a CPSThunk<B, R>
24
+ // calling that with 'k' returns Thunk<R>
25
+ return f(valueA)(k);
26
+ });
27
+ };
28
+ }
29
+ // Helper to lift a direct value into a CPSThunk
30
+ export function valueToCPS(value) {
31
+ return (k) => () => k(value);
32
+ }
33
+ // Helper to lift a regular Thunk into a CPSThunk (if it resolves a simple Thunk)
34
+ export function thunkToCPS(thunk) {
35
+ return (k) => {
36
+ return () => k(trampoline(thunk)); // Trampoline the simple thunk and pass its result to k
37
+ };
38
+ }
@@ -0,0 +1,11 @@
1
+ import { LazyList, PrimitiveValue, Environment, EnvStack, ASTNode } from "yukigo-ast";
2
+ import { Continuation, Thunk } from "./trampoline.js";
3
+ export interface ExpressionEvaluator {
4
+ evaluate<R = PrimitiveValue>(node: ASTNode, cont: Continuation<PrimitiveValue, R>): Thunk<R>;
5
+ }
6
+ export declare function createStream(generator: () => Generator<PrimitiveValue, void, unknown>): LazyList;
7
+ export declare function isArrayOfNumbers(arr: PrimitiveValue[]): arr is number[];
8
+ export declare function generateRange(start: number, end: number, step: number): number[];
9
+ export declare function createEnv(bindings: [string, PrimitiveValue][]): Environment;
10
+ export declare function createGlobalEnv(): EnvStack;
11
+ export declare function getYukigoType(val: PrimitiveValue): string;
@@ -0,0 +1,65 @@
1
+ import { isRuntimeFunction, isRuntimeObject, isLazyList, isRuntimeClass, isRuntimePredicate, } from "yukigo-ast";
2
+ export function createStream(generator) {
3
+ return {
4
+ type: "LazyList",
5
+ generator,
6
+ };
7
+ }
8
+ export function isArrayOfNumbers(arr) {
9
+ for (const item of arr)
10
+ if (typeof item !== "number")
11
+ return false;
12
+ return true;
13
+ }
14
+ export function generateRange(start, end, step) {
15
+ if (step === 0)
16
+ throw new Error("Step cannot be zero in range expression");
17
+ const result = [];
18
+ let current = start;
19
+ if (step > 0) {
20
+ while (current <= end) {
21
+ result.push(current);
22
+ current += step;
23
+ }
24
+ }
25
+ else {
26
+ while (current >= end) {
27
+ result.push(current);
28
+ current += step;
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+ export function createEnv(bindings) {
34
+ const env = new Map();
35
+ for (const [name, value] of bindings)
36
+ env.set(name, value);
37
+ return env;
38
+ }
39
+ export function createGlobalEnv() {
40
+ return {
41
+ head: new Map(),
42
+ tail: null,
43
+ };
44
+ }
45
+ export function getYukigoType(val) {
46
+ if (val === null || val === undefined)
47
+ return "YuNil";
48
+ if (typeof val === "number")
49
+ return "YuNumber";
50
+ if (typeof val === "boolean")
51
+ return "YuBoolean";
52
+ if (typeof val === "string")
53
+ return val.length === 1 ? "YuChar" : "YuString";
54
+ if (Array.isArray(val) || isLazyList(val))
55
+ return "YuList";
56
+ if (isRuntimeFunction(val))
57
+ return "YuFunction";
58
+ if (isRuntimeObject(val))
59
+ return "YuObject";
60
+ if (isRuntimeClass(val))
61
+ return "YuClass";
62
+ if (isRuntimePredicate(val))
63
+ return "YuPredicate";
64
+ return "YuUnknown";
65
+ }
@@ -0,0 +1,25 @@
1
+ import { AST } from "yukigo-ast";
2
+ import { InterpreterConfig } from "../interpreter/components/RuntimeContext.js";
3
+ export type TestStatus = "passed" | "failed" | "error";
4
+ export interface TestReport {
5
+ name: string;
6
+ status: TestStatus;
7
+ message?: string;
8
+ duration?: number;
9
+ children?: TestReport[];
10
+ }
11
+ /**
12
+ * The Tester class provides a high-level API for executing test nodes
13
+ * (Tests and TestGroups) within an AST and generating a detailed report.
14
+ */
15
+ export declare class Tester {
16
+ private ast;
17
+ private config?;
18
+ constructor(ast: AST, config?: InterpreterConfig | undefined);
19
+ /**
20
+ * Executes all Test and TestGroup nodes found at the top level of the provided AST.
21
+ * @param nodes The AST (or subset of nodes) to scan for tests.
22
+ * @returns An array of TestReport objects for each top-level test/group.
23
+ */
24
+ test(nodes: AST): TestReport[];
25
+ }
@@ -0,0 +1,113 @@
1
+ import { Test, TestGroup, TraverseVisitor, } from "yukigo-ast";
2
+ import { Interpreter } from "../interpreter/index.js";
3
+ import { FailedAssert } from "../interpreter/components/TestRunner.js";
4
+ import { UnexpectedNode } from "../utils/helpers.js";
5
+ class TestExecutor extends TraverseVisitor {
6
+ interpreter;
7
+ report = null;
8
+ constructor(interpreter) {
9
+ super();
10
+ this.interpreter = interpreter;
11
+ }
12
+ visitTest(node) {
13
+ const name = this.evaluateName(node.name);
14
+ const start = Date.now();
15
+ try {
16
+ //this.bindParameters(node);
17
+ this.interpreter.evaluate(node);
18
+ this.report = {
19
+ name,
20
+ status: "passed",
21
+ duration: Date.now() - start,
22
+ };
23
+ }
24
+ catch (error) {
25
+ this.report = this.handleError(name, start, error);
26
+ }
27
+ }
28
+ /* private bindParameters(node: Test): void {
29
+ if (!node.args || node.args.length === 0) return;
30
+
31
+ for (const pattern of node.args) {
32
+ if (pattern instanceof VariablePattern) {
33
+ this.interpreter.define(pattern.name.value, null);
34
+ }
35
+ }
36
+ } */
37
+ visitTestGroup(node) {
38
+ const name = this.evaluateName(node.name);
39
+ const start = Date.now();
40
+ try {
41
+ const children = [];
42
+ for (const stmt of node.group.statements) {
43
+ if (stmt instanceof Test || stmt instanceof TestGroup) {
44
+ const visitor = new TestExecutor(this.interpreter);
45
+ stmt.accept(visitor);
46
+ if (visitor.report)
47
+ children.push(visitor.report);
48
+ }
49
+ else {
50
+ this.interpreter.evaluate(stmt);
51
+ }
52
+ }
53
+ const anyFailed = children.some((c) => c.status !== "passed");
54
+ this.report = {
55
+ name,
56
+ status: anyFailed ? "failed" : "passed",
57
+ duration: Date.now() - start,
58
+ children,
59
+ };
60
+ }
61
+ catch (error) {
62
+ this.report = this.handleError(name, start, error);
63
+ }
64
+ }
65
+ evaluateName(nameExpr) {
66
+ try {
67
+ return String(this.interpreter.evaluate(nameExpr));
68
+ }
69
+ catch {
70
+ return "Unknown Test";
71
+ }
72
+ }
73
+ handleError(name, start, error) {
74
+ const duration = Date.now() - start;
75
+ if (error instanceof FailedAssert)
76
+ return { name, status: "failed", message: error.message, duration };
77
+ const message = error instanceof Error ? error.message : String(error);
78
+ return { name, status: "error", message, duration };
79
+ }
80
+ fallback(node) {
81
+ throw new UnexpectedNode(node.constructor.name, "TestExecutor");
82
+ }
83
+ }
84
+ /**
85
+ * The Tester class provides a high-level API for executing test nodes
86
+ * (Tests and TestGroups) within an AST and generating a detailed report.
87
+ */
88
+ export class Tester {
89
+ ast;
90
+ config;
91
+ constructor(ast, config) {
92
+ this.ast = ast;
93
+ this.config = config;
94
+ }
95
+ /**
96
+ * Executes all Test and TestGroup nodes found at the top level of the provided AST.
97
+ * @param nodes The AST (or subset of nodes) to scan for tests.
98
+ * @returns An array of TestReport objects for each top-level test/group.
99
+ */
100
+ test(nodes) {
101
+ const reports = [];
102
+ for (const node of nodes) {
103
+ if (node instanceof Test || node instanceof TestGroup) {
104
+ const interpreter = new Interpreter(this.ast, this.config);
105
+ const visitor = new TestExecutor(interpreter);
106
+ node.accept(visitor);
107
+ if (visitor.report)
108
+ reports.push(visitor.report);
109
+ }
110
+ }
111
+ return reports;
112
+ }
113
+ }
@@ -0,0 +1,13 @@
1
+ import { InspectionRule } from "../analyzer/index.js";
2
+ export declare class UnexpectedNode extends Error {
3
+ constructor(nodeCons: string, context: string);
4
+ }
5
+ /**
6
+ * Translates Mulang inspections (YAML format) to an array of `InspectionRule` objects.
7
+ * @param mulangYamlString The Mulang inspection syntax as a YAML string.
8
+ * @returns An array of InspectionRule objects.
9
+ */
10
+ export declare class MulangAdapter {
11
+ translateMulangInspection(mulangInspection: any): InspectionRule;
12
+ translateMulangExpectations(mulangYamlString: string): InspectionRule[];
13
+ }
@@ -0,0 +1,52 @@
1
+ import { parseDocument } from "yaml";
2
+ export class UnexpectedNode extends Error {
3
+ constructor(nodeCons, context) {
4
+ super(`${nodeCons} not expected in ${context}.`);
5
+ }
6
+ }
7
+ const isValidFormat = (inspection) => typeof inspection === "object" &&
8
+ "inspection" in inspection &&
9
+ "binding" in inspection;
10
+ /**
11
+ * Translates Mulang inspections (YAML format) to an array of `InspectionRule` objects.
12
+ * @param mulangYamlString The Mulang inspection syntax as a YAML string.
13
+ * @returns An array of InspectionRule objects.
14
+ */
15
+ export class MulangAdapter {
16
+ translateMulangInspection(mulangInspection) {
17
+ if (!isValidFormat(mulangInspection))
18
+ throw new Error(`Skipping malformed Mulang inspection entry: ${mulangInspection}`);
19
+ const inspection = mulangInspection.inspection.split(":");
20
+ const expected = inspection[0] !== "Not" && inspection[0] !== "Except";
21
+ const args = inspection.slice(expected ? 1 : 2);
22
+ return {
23
+ inspection: expected ? inspection[0] : inspection[1],
24
+ expected,
25
+ args,
26
+ binding: mulangInspection.binding,
27
+ };
28
+ }
29
+ translateMulangExpectations(mulangYamlString) {
30
+ if (!mulangYamlString)
31
+ return [];
32
+ const parsedYaml = parseDocument(mulangYamlString).toJS();
33
+ if (!parsedYaml)
34
+ return [];
35
+ let expectations = [];
36
+ if (Array.isArray(parsedYaml)) {
37
+ expectations = parsedYaml;
38
+ }
39
+ else if (Array.isArray(parsedYaml.expectations)) {
40
+ expectations = parsedYaml.expectations;
41
+ }
42
+ else {
43
+ throw new Error("Invalid Mulang YAML structure. Expected 'expectations' to be an array.");
44
+ }
45
+ const inspectionRules = [];
46
+ for (const mulangInspection of expectations) {
47
+ const inspection = this.translateMulangInspection(mulangInspection);
48
+ inspectionRules.push(inspection);
49
+ }
50
+ return inspectionRules;
51
+ }
52
+ }
@@ -5,6 +5,10 @@ import { InspectionRule } from "../analyzer/index.js";
5
5
  * @returns An array of InspectionRule objects.
6
6
  */
7
7
  export declare class MulangAdapter {
8
- translateMulangInspection(mulangInspection: any): InspectionRule;
8
+ translateMulangInspection(mulangInspection: unknown): InspectionRule;
9
9
  translateMulangExpectations(mulangYamlString: string): InspectionRule[];
10
+ private transformToV2;
11
+ private parseNegation;
12
+ private applyV0ToV2;
13
+ private retrieveArguments;
10
14
  }
@@ -1,4 +1,19 @@
1
1
  import { parseDocument } from "yaml";
2
+ const declareMap = {
3
+ HasBinding: "Declares",
4
+ HasTypeDeclaration: "DeclaresTypeAlias",
5
+ HasTypeSignature: "DeclaresTypeSignature",
6
+ HasVariable: "DeclaresVariable",
7
+ };
8
+ const V0_HAS_INSPECTIONS = new Set([
9
+ "HasBinding", "HasTypeDeclaration", "HasTypeSignature", "HasVariable",
10
+ "HasArity", "HasDirectRecursion", "HasComposition", "HasComprehension",
11
+ "HasForeach", "HasIf", "HasGuards", "HasConditional", "HasLambda",
12
+ "HasRepeat", "HasWhile", "HasUsage", "HasAnonymousVariable",
13
+ "HasNot", "HasForall", "HasFindall",
14
+ ]);
15
+ const NEGATION_PREFIXES = new Set(["Not", "Except"]);
16
+ const SUFFIX_ARGS = new Set(["like", "except"]);
2
17
  const isValidFormat = (inspection) => typeof inspection === "object" &&
3
18
  "inspection" in inspection &&
4
19
  "binding" in inspection;
@@ -11,14 +26,17 @@ export class MulangAdapter {
11
26
  translateMulangInspection(mulangInspection) {
12
27
  if (!isValidFormat(mulangInspection))
13
28
  throw new Error(`Skipping malformed Mulang inspection entry: ${mulangInspection}`);
14
- const inspection = mulangInspection.inspection.split(":");
15
- const expected = inspection[0] !== "Not" && inspection[0] !== "Except";
16
- const args = inspection.slice(expected ? 1 : 2);
29
+ // transforms Mulang v0 to v2
30
+ const { inspection, expected, binding, args } = this.transformToV2(mulangInspection);
31
+ const { resolvedArgs, targetSuffix, matcher } = this.retrieveArguments(args);
17
32
  return {
18
- inspection: expected ? inspection[0] : inspection[1],
33
+ inspection,
19
34
  expected,
20
- args,
21
- binding: mulangInspection.binding,
35
+ binding,
36
+ args: resolvedArgs,
37
+ // these should not exist in the InspectionRule if they are undefined
38
+ ...(targetSuffix !== undefined && { targetSuffix }),
39
+ ...(matcher !== undefined && { matcher }),
22
40
  };
23
41
  }
24
42
  translateMulangExpectations(mulangYamlString) {
@@ -44,4 +62,62 @@ export class MulangAdapter {
44
62
  }
45
63
  return inspectionRules;
46
64
  }
65
+ transformToV2(mulangInspection) {
66
+ const parts = mulangInspection.inspection.split(":");
67
+ const { expected, inspection, args: parsedArgs, } = this.parseNegation(parts);
68
+ const args = mulangInspection.args ?? parsedArgs;
69
+ const v2 = this.applyV0ToV2(inspection, args, mulangInspection.binding);
70
+ return { expected, ...v2 };
71
+ }
72
+ parseNegation(parts) {
73
+ const negated = NEGATION_PREFIXES.has(parts[0]);
74
+ return {
75
+ expected: !negated,
76
+ inspection: parts[negated ? 1 : 0],
77
+ args: parts.slice(negated ? 2 : 1),
78
+ };
79
+ }
80
+ applyV0ToV2(inspection, args, binding) {
81
+ if (inspection in declareMap)
82
+ return {
83
+ inspection: declareMap[inspection],
84
+ ...promoteBindingToTarget(args, binding),
85
+ };
86
+ if (inspection === "HasArity")
87
+ return {
88
+ inspection: "DeclaresComputationWithArity",
89
+ args: [args[0], ...(binding && binding !== "*" ? [binding] : [])],
90
+ binding: "*",
91
+ };
92
+ if (V0_HAS_INSPECTIONS.has(inspection))
93
+ return { inspection: inspection === "HasUsage" ? "Uses" : inspection.replace(/^Has/, "Uses"), args, binding };
94
+ return { inspection, args, binding };
95
+ }
96
+ retrieveArguments(args) {
97
+ const targetSuffix = args.find((a) => SUFFIX_ARGS.has(a));
98
+ const matcherIndex = args.findIndex((a) => a.startsWith("With"));
99
+ const matcher = matcherIndex !== -1
100
+ ? {
101
+ type: "with_" + args[matcherIndex].slice(4).toLowerCase(),
102
+ value: args[matcherIndex + 1],
103
+ }
104
+ : undefined;
105
+ const consumed = new Set([
106
+ ...(targetSuffix ? [targetSuffix] : []),
107
+ ...(matcherIndex !== -1
108
+ ? [args[matcherIndex], args[matcherIndex + 1]]
109
+ : []),
110
+ ]);
111
+ const resolvedArgs = args.filter((a) => !consumed.has(a));
112
+ return {
113
+ targetSuffix: targetSuffix ??
114
+ (resolvedArgs.length > 0 ? "named" : undefined),
115
+ matcher,
116
+ resolvedArgs,
117
+ };
118
+ }
47
119
  }
120
+ const promoteBindingToTarget = (args, binding) => ({
121
+ args: binding && binding !== "*" ? [binding, ...args] : args,
122
+ binding: "*",
123
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yukigo",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,7 +14,9 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "typescript": "^5.9.2",
17
- "yaml": "^2.8.0",
17
+ "yaml": "^2.8.0"
18
+ },
19
+ "peerDependencies": {
18
20
  "yukigo-ast": "^0.2.0"
19
21
  },
20
22
  "devDependencies": {
@@ -19,11 +19,20 @@ export type AnalysisResult = {
19
19
  error?: string;
20
20
  };
21
21
 
22
+ export type TargetSuffix = "named" | "like" | "except";
23
+
24
+ export type InspectionMatcher = {
25
+ type: string;
26
+ value?: string;
27
+ };
28
+
22
29
  export type InspectionRule = {
23
30
  inspection: string;
24
31
  binding?: string;
25
32
  args?: string[];
26
33
  expected: boolean;
34
+ targetSuffix?: TargetSuffix;
35
+ matcher?: InspectionMatcher;
27
36
  };
28
37
 
29
38
  export const DefaultInspectionSet: InspectionMap = {
@@ -119,10 +119,6 @@ export class Declares extends ScopedVisitor {
119
119
  visitTypeAlias(node: TypeAlias): void {
120
120
  this.check(node);
121
121
  }
122
-
123
- visitTypeSignature(node: TypeSignature): void {
124
- this.check(node);
125
- }
126
122
  }
127
123
  @AutoScoped
128
124
  export class DeclaresComputation extends ScopedVisitor {
@@ -234,7 +230,7 @@ export class DeclaresRecursively extends ScopedVisitor {
234
230
  throw new StopTraversalException();
235
231
  }
236
232
  }
237
- export class HasDirectRecursion extends TraverseVisitor {
233
+ export class UsesDirectRecursion extends TraverseVisitor {
238
234
  private isInsideBody: boolean = false;
239
235
  constructor(private readonly binding: string) {
240
236
  super();
@@ -553,7 +549,7 @@ export const genericInspections: Record<string, VisitorConstructor> = {
553
549
  DeclaresEntryPoint: DeclaresEntryPoint,
554
550
  DeclaresFunction: DeclaresFunction,
555
551
  DeclaresRecursively: DeclaresRecursively,
556
- HasDirectRecursion: HasDirectRecursion,
552
+ UsesDirectRecursion: UsesDirectRecursion,
557
553
  DeclaresTypeAlias: DeclaresTypeAlias,
558
554
  DeclaresTypeSignature: DeclaresTypeSignature,
559
555
  HasTypeSignature: DeclaresTypeSignature,