yukigo 0.3.2 → 0.3.6

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/CHANGELOG.md CHANGED
@@ -1,68 +1,110 @@
1
- ## 0.3.2 (2026-08-04)
1
+ ## 0.3.6 (2026-09-13)
2
2
 
3
- ### 🩹 Fixes
3
+ ### 🧱 Updated Dependencies
4
4
 
5
- - **yukigo:** fix v0 Mulang Inspections translation ([2995ccc](https://github.com/miyukiproject/yukigo/commit/2995ccc))
5
+ - Updated yukigo-ast to 0.3.6
6
+
7
+ ## 0.3.5 (2026-09-13)
6
8
 
7
9
  ### 🧱 Updated Dependencies
8
10
 
9
- - Updated yukigo-ast to 0.3.2
11
+ - Updated yukigo-ast to 0.3.5
10
12
 
11
- ### ❤️ Thank You
13
+ ## 0.3.4 (2026-08-05)
12
14
 
13
- - noiseArch
15
+ ### 🩹 Fixes
14
16
 
15
- ## 0.3.1 (2026-08-01)
17
+ - **yukigo:** Fix MulangAdapter target suffix and matcher ([4d0a988](https://github.com/miyukiproject/yukigo/commit/4d0a988))
18
+ - **yukigo:** fix v0 Mulang Inspections translation ([2995ccc](https://github.com/miyukiproject/yukigo/commit/2995ccc))
16
19
 
17
20
  ### 🧱 Updated Dependencies
18
21
 
19
- - Updated yukigo-ast to 0.3.1
22
+ - Updated yukigo-ast to 0.3.4
20
23
 
21
- ## 0.3.0 (2026-08-01)
24
+ ### ❤️ Thank You
22
25
 
23
- ### 🚀 Features
26
+ - noiseArch
24
27
 
25
- - Interpreter NativeBody support & YukigoHooks ([0b854b4](https://github.com/miyukiproject/yukigo/commit/0b854b4))
26
- - **interpreter:** implement semantic error handling and stack traces ([b61768f](https://github.com/miyukiproject/yukigo/commit/b61768f))
27
- - **interpreter:** integrate Kernel as the primary execution engine ([503af3d](https://github.com/miyukiproject/yukigo/commit/503af3d))
28
- - **interpreter:** introduce YukigoKernel and command-based infrastructure ([9380892](https://github.com/miyukiproject/yukigo/commit/9380892))
29
- - **yukigo:** implement and test YukigoKernel ([3d42ac4](https://github.com/miyukiproject/yukigo/commit/3d42ac4))
28
+ ## 0.3.3 (2026-08-05)
30
29
 
31
30
  ### 🩹 Fixes
32
31
 
33
- - **yukigo-e2e:** fixes to pass E2E tests ([02d895f](https://github.com/miyukiproject/yukigo/commit/02d895f))
34
- - **yukigo:** allow concatenation with Plus operator ([7799416](https://github.com/miyukiproject/yukigo/commit/7799416))
35
- - **yukigo:** fix circular dependency ([64534a8](https://github.com/miyukiproject/yukigo/commit/64534a8))
36
- - **yukigo:** failed test didnt throw bc now it handles 1 + "a" ([23ebeb6](https://github.com/miyukiproject/yukigo/commit/23ebeb6))
37
- - **yukigo:** zero-arity functions and Wollok bugs ([995dad4](https://github.com/miyukiproject/yukigo/commit/995dad4))
38
- - **yukigo:** ObjectRuntime error should return RaiseCommand not FailCommand ([ccb31fa](https://github.com/miyukiproject/yukigo/commit/ccb31fa))
39
- - **yukigo:** method registration with arity to allow overload ([8ffcb36](https://github.com/miyukiproject/yukigo/commit/8ffcb36))
40
- - TypeSignature is not a Binding ([13c2c5a](https://github.com/miyukiproject/yukigo/commit/13c2c5a))
41
- - **yukigo:** support If and Call nodes in LogicEngine ([a0f4086](https://github.com/miyukiproject/yukigo/commit/a0f4086))
42
- - **yukigo:** fixed functional tester was evaluating logic tests ([914310e](https://github.com/miyukiproject/yukigo/commit/914310e))
43
- - **yukigo:** solved scope leak at logical binary operations ([8779899](https://github.com/miyukiproject/yukigo/commit/8779899))
44
- - ToString operator ([f5284ab](https://github.com/miyukiproject/yukigo/commit/f5284ab))
45
- - **yukigo:** fix pattern matching in FunctionRuntime ([467cdf7](https://github.com/miyukiproject/yukigo/commit/467cdf7))
46
- - **yukigo:** fix bitwise negation ([b13ef4d](https://github.com/miyukiproject/yukigo/commit/b13ef4d))
47
- - **yukigo:** fix asynchronous logic unification and parameter resolution ([e467068](https://github.com/miyukiproject/yukigo/commit/e467068))
48
- - **yukigo:** arreglo operaciones y sustitucion logica ([c95f33f](https://github.com/miyukiproject/yukigo/commit/c95f33f))
49
- - fix parsers grammar compilation to export to .cjs ([ef59727](https://github.com/miyukiproject/yukigo/commit/ef59727))
50
- - **yukigo:** fix tests to reflect correct representations ([76b1512](https://github.com/miyukiproject/yukigo/commit/76b1512))
51
- - **yukigo:** fix Visitor and LazyRuntime to use new correct methods from context ([0eb9168](https://github.com/miyukiproject/yukigo/commit/0eb9168))
52
- - **yukigo:** arreglos en logicengine ([0604555](https://github.com/miyukiproject/yukigo/commit/0604555))
53
- - **yukigo:** fix mini error in oop test ([b9fd07e](https://github.com/miyukiproject/yukigo/commit/b9fd07e))
54
- - **yukigo:** removed unwanted error handling ([0d1ff15](https://github.com/miyukiproject/yukigo/commit/0d1ff15))
55
- - **yukigo:** replace instanceof with .is method in analyzer ([d953413](https://github.com/miyukiproject/yukigo/commit/d953413))
56
- - **yukigo:** add InterpreterConfig as export ([280146c](https://github.com/miyukiproject/yukigo/commit/280146c))
32
+ - **yukigo:** Fix MulangAdapter target suffix and matcher ([4d0a988](https://github.com/miyukiproject/yukigo/commit/4d0a988))
33
+ - **yukigo:** fix v0 Mulang Inspections translation ([2995ccc](https://github.com/miyukiproject/yukigo/commit/2995ccc))
57
34
 
58
35
  ### 🧱 Updated Dependencies
59
36
 
60
- - Updated yukigo-ast to 0.3.0
37
+ - Updated yukigo-ast to 0.3.3
61
38
 
62
39
  ### ❤️ Thank You
63
40
 
64
41
  - noiseArch
65
42
 
43
+ ## 0.3.2 (2026-08-05)
44
+
45
+ ### 🩹 Fixes
46
+
47
+ - **yukigo:** fix v0 Mulang Inspections translation ([2995ccc](https://github.com/miyukiproject/yukigo/commit/2995ccc))
48
+
49
+ ### 🧱 Updated Dependencies
50
+
51
+ - Updated yukigo-ast to 0.3.2
52
+
53
+ ### ❤️ Thank You
54
+
55
+ - noiseArch
56
+
57
+ ## 0.3.1 (2026-08-01)
58
+
59
+ ### 🧱 Updated Dependencies
60
+
61
+ - Updated yukigo-ast to 0.3.1
62
+
63
+ ## 0.3.0 (2026-08-01)
64
+
65
+ ### 🚀 Features
66
+
67
+ - Interpreter NativeBody support & YukigoHooks ([0b854b4](https://github.com/miyukiproject/yukigo/commit/0b854b4))
68
+ - **interpreter:** implement semantic error handling and stack traces ([b61768f](https://github.com/miyukiproject/yukigo/commit/b61768f))
69
+ - **interpreter:** integrate Kernel as the primary execution engine ([503af3d](https://github.com/miyukiproject/yukigo/commit/503af3d))
70
+ - **interpreter:** introduce YukigoKernel and command-based infrastructure ([9380892](https://github.com/miyukiproject/yukigo/commit/9380892))
71
+ - **yukigo:** implement and test YukigoKernel ([3d42ac4](https://github.com/miyukiproject/yukigo/commit/3d42ac4))
72
+
73
+ ### 🩹 Fixes
74
+
75
+ - **yukigo-e2e:** fixes to pass E2E tests ([02d895f](https://github.com/miyukiproject/yukigo/commit/02d895f))
76
+ - **yukigo:** allow concatenation with Plus operator ([7799416](https://github.com/miyukiproject/yukigo/commit/7799416))
77
+ - **yukigo:** fix circular dependency ([64534a8](https://github.com/miyukiproject/yukigo/commit/64534a8))
78
+ - **yukigo:** failed test didnt throw bc now it handles 1 + "a" ([23ebeb6](https://github.com/miyukiproject/yukigo/commit/23ebeb6))
79
+ - **yukigo:** zero-arity functions and Wollok bugs ([995dad4](https://github.com/miyukiproject/yukigo/commit/995dad4))
80
+ - **yukigo:** ObjectRuntime error should return RaiseCommand not FailCommand ([ccb31fa](https://github.com/miyukiproject/yukigo/commit/ccb31fa))
81
+ - **yukigo:** method registration with arity to allow overload ([8ffcb36](https://github.com/miyukiproject/yukigo/commit/8ffcb36))
82
+ - TypeSignature is not a Binding ([13c2c5a](https://github.com/miyukiproject/yukigo/commit/13c2c5a))
83
+ - **yukigo:** support If and Call nodes in LogicEngine ([a0f4086](https://github.com/miyukiproject/yukigo/commit/a0f4086))
84
+ - **yukigo:** fixed functional tester was evaluating logic tests ([914310e](https://github.com/miyukiproject/yukigo/commit/914310e))
85
+ - **yukigo:** solved scope leak at logical binary operations ([8779899](https://github.com/miyukiproject/yukigo/commit/8779899))
86
+ - ToString operator ([f5284ab](https://github.com/miyukiproject/yukigo/commit/f5284ab))
87
+ - **yukigo:** fix pattern matching in FunctionRuntime ([467cdf7](https://github.com/miyukiproject/yukigo/commit/467cdf7))
88
+ - **yukigo:** fix bitwise negation ([b13ef4d](https://github.com/miyukiproject/yukigo/commit/b13ef4d))
89
+ - **yukigo:** fix asynchronous logic unification and parameter resolution ([e467068](https://github.com/miyukiproject/yukigo/commit/e467068))
90
+ - **yukigo:** arreglo operaciones y sustitucion logica ([c95f33f](https://github.com/miyukiproject/yukigo/commit/c95f33f))
91
+ - fix parsers grammar compilation to export to .cjs ([ef59727](https://github.com/miyukiproject/yukigo/commit/ef59727))
92
+ - **yukigo:** fix tests to reflect correct representations ([76b1512](https://github.com/miyukiproject/yukigo/commit/76b1512))
93
+ - **yukigo:** fix Visitor and LazyRuntime to use new correct methods from context ([0eb9168](https://github.com/miyukiproject/yukigo/commit/0eb9168))
94
+ - **yukigo:** arreglos en logicengine ([0604555](https://github.com/miyukiproject/yukigo/commit/0604555))
95
+ - **yukigo:** fix mini error in oop test ([b9fd07e](https://github.com/miyukiproject/yukigo/commit/b9fd07e))
96
+ - **yukigo:** removed unwanted error handling ([0d1ff15](https://github.com/miyukiproject/yukigo/commit/0d1ff15))
97
+ - **yukigo:** replace instanceof with .is method in analyzer ([d953413](https://github.com/miyukiproject/yukigo/commit/d953413))
98
+ - **yukigo:** add InterpreterConfig as export ([280146c](https://github.com/miyukiproject/yukigo/commit/280146c))
99
+
100
+ ### 🧱 Updated Dependencies
101
+
102
+ - Updated yukigo-ast to 0.3.0
103
+
104
+ ### ❤️ Thank You
105
+
106
+ - noiseArch
107
+
66
108
  ## 0.2.3 (2026-05-02)
67
109
 
68
110
  ### 🩹 Fixes
@@ -6,11 +6,18 @@ export type AnalysisResult = {
6
6
  actual: boolean;
7
7
  error?: string;
8
8
  };
9
+ export type TargetSuffix = "named" | "like" | "except";
10
+ export type InspectionMatcher = {
11
+ type: string;
12
+ value?: string;
13
+ };
9
14
  export type InspectionRule = {
10
15
  inspection: string;
11
16
  binding?: string;
12
17
  args?: string[];
13
18
  expected: boolean;
19
+ targetSuffix?: TargetSuffix;
20
+ matcher?: InspectionMatcher;
14
21
  };
15
22
  export declare const DefaultInspectionSet: InspectionMap;
16
23
  export declare const DefaultSmellsSet: InspectionMap;
@@ -16,7 +16,10 @@ export interface InterpreterConfig {
16
16
  * @returns An array of InspectionRule objects.
17
17
  */
18
18
  export declare class MulangAdapter {
19
- translateMulangInspection(mulangInspection: any): InspectionRule;
19
+ translateMulangInspection(mulangInspection: unknown): InspectionRule;
20
20
  translateMulangExpectations(mulangYamlString: string): InspectionRule[];
21
- private translateV0Inspection;
21
+ private transformToV2;
22
+ private parseNegation;
23
+ private applyV0ToV2;
24
+ private retrieveArguments;
22
25
  }
@@ -1,28 +1,19 @@
1
1
  import { parseDocument } from "yaml";
2
- const V0_INSPECTIONS_Has = [
3
- "HasComposition",
4
- "HasComprehension",
5
- "HasForeach",
6
- "HasIf",
7
- "HasGuards",
8
- "HasConditional",
9
- "HasLambda",
10
- "HasRepeat",
11
- "HasWhile",
12
- "HasAnonymousVariable",
13
- "HasNot",
14
- "HasForall",
15
- "HasFindall",
16
- ];
17
- const V0_INSPECTIONS_Declares = new Map([
18
- ["HasBinding", "Declares"],
19
- ["HasTypeDeclaration", "DeclaresTypeAlias"],
20
- ["HasTypeSignature", "DeclaresTypeSignature"],
21
- ["HasVariable", "DeclaresVariable"],
22
- ["HasArity", "DeclaresComputationWithArity"],
23
- ["HasDirectRecursion", "DeclaresRecursively"],
24
- ["HasUsage", "Uses"],
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",
25
14
  ]);
15
+ const NEGATION_PREFIXES = new Set(["Not", "Except"]);
16
+ const SUFFIX_ARGS = new Set(["like", "except"]);
26
17
  const isValidFormat = (inspection) => typeof inspection === "object" &&
27
18
  "inspection" in inspection &&
28
19
  "binding" in inspection;
@@ -35,16 +26,17 @@ export class MulangAdapter {
35
26
  translateMulangInspection(mulangInspection) {
36
27
  if (!isValidFormat(mulangInspection))
37
28
  throw new Error(`Skipping malformed Mulang inspection entry: ${mulangInspection}`);
38
- const inspection = mulangInspection.inspection.split(":");
39
- const expected = inspection[0] !== "Not" && inspection[0] !== "Except";
40
- const args = inspection.slice(expected ? 1 : 2);
41
- const inspectionV0 = expected ? inspection[0] : inspection[1];
42
- const inspectionV2 = this.translateV0Inspection(inspectionV0);
29
+ // transforms Mulang v0 to v2
30
+ const { inspection, expected, binding, args } = this.transformToV2(mulangInspection);
31
+ const { resolvedArgs, targetSuffix, matcher } = this.retrieveArguments(args);
43
32
  return {
44
- inspection: inspectionV2,
33
+ inspection,
45
34
  expected,
46
- args,
47
- 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 }),
48
40
  };
49
41
  }
50
42
  translateMulangExpectations(mulangYamlString) {
@@ -63,14 +55,69 @@ export class MulangAdapter {
63
55
  else {
64
56
  throw new Error("Invalid Mulang YAML structure. Expected 'expectations' to be an array.");
65
57
  }
66
- const inspectionRules = expectations.map((insp) => this.translateMulangInspection(insp));
58
+ const inspectionRules = [];
59
+ for (const mulangInspection of expectations) {
60
+ const inspection = this.translateMulangInspection(mulangInspection);
61
+ inspectionRules.push(inspection);
62
+ }
67
63
  return inspectionRules;
68
64
  }
69
- translateV0Inspection(inspection) {
70
- if (V0_INSPECTIONS_Declares.has(inspection))
71
- return V0_INSPECTIONS_Declares.get(inspection);
72
- if (V0_INSPECTIONS_Has.includes(inspection))
73
- return inspection.replace("Has", "Uses");
74
- return inspection;
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
+ };
75
118
  }
76
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.3.2",
3
+ "version": "0.3.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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 = {
@@ -1,113 +1,181 @@
1
- import { parseDocument } from "yaml";
2
- import { InspectionRule } from "../analyzer/index.js";
3
- import { NativeExtension } from "../interpreter/utils.js";
4
- import { YukigoHook } from "../interpreter/components/hooks/YukigoHook.js";
5
-
6
- const V0_INSPECTIONS_Has = [
7
- "HasComposition",
8
- "HasComprehension",
9
- "HasForeach",
10
- "HasIf",
11
- "HasGuards",
12
- "HasConditional",
13
- "HasLambda",
14
- "HasRepeat",
15
- "HasWhile",
16
- "HasAnonymousVariable",
17
- "HasNot",
18
- "HasForall",
19
- "HasFindall",
20
- ];
21
-
22
- const V0_INSPECTIONS_Declares: Map<string, string> = new Map([
23
- ["HasBinding", "Declares"],
24
- ["HasTypeDeclaration", "DeclaresTypeAlias"],
25
- ["HasTypeSignature", "DeclaresTypeSignature"],
26
- ["HasVariable", "DeclaresVariable"],
27
- ["HasArity", "DeclaresComputationWithArity"],
28
- ["HasDirectRecursion", "DeclaresRecursively"],
29
- ["HasUsage", "Uses"],
30
- ]);
31
-
32
- type MulangInspection = {
33
- inspection: string;
34
- binding: string;
35
- };
36
-
37
- export type LogicSearchMode = "first" | "all" | "stream";
38
- export interface InterpreterConfig {
39
- nativeProviders: Map<string, NativeExtension>;
40
- lazyLoading: boolean;
41
- debug: boolean;
42
- outputMode: LogicSearchMode;
43
- mutability: boolean;
44
- hooks?: YukigoHook[];
45
- }
46
-
47
- const isValidFormat = (inspection: any): inspection is MulangInspection =>
48
- typeof inspection === "object" &&
49
- "inspection" in inspection &&
50
- "binding" in inspection;
51
-
52
- /**
53
- * Translates Mulang inspections (YAML format) to an array of `InspectionRule` objects.
54
- * @param mulangYamlString The Mulang inspection syntax as a YAML string.
55
- * @returns An array of InspectionRule objects.
56
- */
57
- export class MulangAdapter {
58
- public translateMulangInspection(mulangInspection: any): InspectionRule {
59
- if (!isValidFormat(mulangInspection))
60
- throw new Error(
61
- `Skipping malformed Mulang inspection entry: ${mulangInspection}`,
62
- );
63
-
64
- const inspection: string[] = mulangInspection.inspection.split(":");
65
- const expected: boolean =
66
- inspection[0] !== "Not" && inspection[0] !== "Except";
67
- const args: string[] = inspection.slice(expected ? 1 : 2);
68
-
69
- const inspectionV0 = expected ? inspection[0] : inspection[1];
70
- const inspectionV2 = this.translateV0Inspection(inspectionV0);
71
- return {
72
- inspection: inspectionV2,
73
- expected,
74
- args,
75
- binding: mulangInspection.binding,
76
- };
77
- }
78
-
79
- public translateMulangExpectations(
80
- mulangYamlString: string,
81
- ): InspectionRule[] {
82
- if (!mulangYamlString) return [];
83
- const parsedYaml = parseDocument(mulangYamlString).toJS();
84
-
85
- if (!parsedYaml) return [];
86
-
87
- let expectations: any[] = [];
88
- if (Array.isArray(parsedYaml)) {
89
- expectations = parsedYaml;
90
- } else if (Array.isArray(parsedYaml.expectations)) {
91
- expectations = parsedYaml.expectations;
92
- } else {
93
- throw new Error(
94
- "Invalid Mulang YAML structure. Expected 'expectations' to be an array.",
95
- );
96
- }
97
-
98
- const inspectionRules: InspectionRule[] = expectations.map((insp) =>
99
- this.translateMulangInspection(insp),
100
- );
101
- return inspectionRules;
102
- }
103
-
104
- private translateV0Inspection(inspection: string): string {
105
- if (V0_INSPECTIONS_Declares.has(inspection))
106
- return V0_INSPECTIONS_Declares.get(inspection)!;
107
-
108
- if (V0_INSPECTIONS_Has.includes(inspection))
109
- return inspection.replace("Has", "Uses");
110
-
111
- return inspection;
112
- }
113
- }
1
+ import { parseDocument } from "yaml";
2
+ import {
3
+ InspectionRule,
4
+ TargetSuffix,
5
+ } from "../analyzer/index.js";
6
+ import { NativeExtension } from "../interpreter/utils.js";
7
+ import { YukigoHook } from "../interpreter/components/hooks/YukigoHook.js";
8
+
9
+ const declareMap: Record<string, string> = {
10
+ HasBinding: "Declares",
11
+ HasTypeDeclaration: "DeclaresTypeAlias",
12
+ HasTypeSignature: "DeclaresTypeSignature",
13
+ HasVariable: "DeclaresVariable",
14
+ };
15
+ const V0_HAS_INSPECTIONS = new Set([
16
+ "HasBinding", "HasTypeDeclaration", "HasTypeSignature", "HasVariable",
17
+ "HasArity", "HasDirectRecursion", "HasComposition", "HasComprehension",
18
+ "HasForeach", "HasIf", "HasGuards", "HasConditional", "HasLambda",
19
+ "HasRepeat", "HasWhile", "HasUsage", "HasAnonymousVariable",
20
+ "HasNot", "HasForall", "HasFindall",
21
+ ]);
22
+
23
+ type MulangInspection = {
24
+ inspection: string;
25
+ binding: string;
26
+ args?: string[];
27
+ };
28
+
29
+ export type LogicSearchMode = "first" | "all" | "stream";
30
+ export interface InterpreterConfig {
31
+ nativeProviders: Map<string, NativeExtension>;
32
+ lazyLoading: boolean;
33
+ debug: boolean;
34
+ outputMode: LogicSearchMode;
35
+ mutability: boolean;
36
+ hooks?: YukigoHook[];
37
+ }
38
+
39
+ const NEGATION_PREFIXES = new Set(["Not", "Except"]);
40
+ const SUFFIX_ARGS = new Set<TargetSuffix>(["like", "except"]);
41
+
42
+ const isValidFormat = (inspection: any): inspection is MulangInspection =>
43
+ typeof inspection === "object" &&
44
+ "inspection" in inspection &&
45
+ "binding" in inspection;
46
+
47
+ /**
48
+ * Translates Mulang inspections (YAML format) to an array of `InspectionRule` objects.
49
+ * @param mulangYamlString The Mulang inspection syntax as a YAML string.
50
+ * @returns An array of InspectionRule objects.
51
+ */
52
+ export class MulangAdapter {
53
+ public translateMulangInspection(mulangInspection: unknown): InspectionRule {
54
+ if (!isValidFormat(mulangInspection))
55
+ throw new Error(
56
+ `Skipping malformed Mulang inspection entry: ${mulangInspection}`,
57
+ );
58
+
59
+ // transforms Mulang v0 to v2
60
+ const { inspection, expected, binding, args } =
61
+ this.transformToV2(mulangInspection);
62
+
63
+ const { resolvedArgs, targetSuffix, matcher } =
64
+ this.retrieveArguments(args);
65
+
66
+ return {
67
+ inspection,
68
+ expected,
69
+ binding,
70
+ args: resolvedArgs,
71
+ // these should not exist in the InspectionRule if they are undefined
72
+ ...(targetSuffix !== undefined && { targetSuffix }),
73
+ ...(matcher !== undefined && { matcher }),
74
+ };
75
+ }
76
+
77
+ public translateMulangExpectations(
78
+ mulangYamlString: string
79
+ ): InspectionRule[] {
80
+ if (!mulangYamlString) return [];
81
+ const parsedYaml = parseDocument(mulangYamlString).toJS();
82
+
83
+ if (!parsedYaml) return [];
84
+
85
+ let expectations: any[] = [];
86
+ if (Array.isArray(parsedYaml)) {
87
+ expectations = parsedYaml;
88
+ } else if (Array.isArray(parsedYaml.expectations)) {
89
+ expectations = parsedYaml.expectations;
90
+ } else {
91
+ throw new Error(
92
+ "Invalid Mulang YAML structure. Expected 'expectations' to be an array."
93
+ );
94
+ }
95
+
96
+ const inspectionRules: InspectionRule[] = [];
97
+
98
+ for (const mulangInspection of expectations) {
99
+ const inspection = this.translateMulangInspection(mulangInspection);
100
+ inspectionRules.push(inspection);
101
+ }
102
+
103
+ return inspectionRules;
104
+ }
105
+
106
+ private transformToV2(mulangInspection: MulangInspection) {
107
+ const parts = mulangInspection.inspection.split(":");
108
+ const {
109
+ expected,
110
+ inspection,
111
+ args: parsedArgs,
112
+ } = this.parseNegation(parts);
113
+ const args = mulangInspection.args ?? parsedArgs;
114
+ const v2 = this.applyV0ToV2(inspection, args, mulangInspection.binding);
115
+ return { expected, ...v2 };
116
+ }
117
+ private parseNegation(parts: string[]) {
118
+ const negated = NEGATION_PREFIXES.has(parts[0]);
119
+ return {
120
+ expected: !negated,
121
+ inspection: parts[negated ? 1 : 0],
122
+ args: parts.slice(negated ? 2 : 1),
123
+ };
124
+ }
125
+ private applyV0ToV2(inspection: string, args: string[], binding: string) {
126
+ if (inspection in declareMap)
127
+ return {
128
+ inspection: declareMap[inspection],
129
+ ...promoteBindingToTarget(args, binding),
130
+ };
131
+
132
+ if (inspection === "HasArity")
133
+ return {
134
+ inspection: "DeclaresComputationWithArity",
135
+ args: [args[0], ...(binding && binding !== "*" ? [binding] : [])],
136
+ binding: "*",
137
+ };
138
+
139
+ if (V0_HAS_INSPECTIONS.has(inspection))
140
+ return { inspection: inspection === "HasUsage" ? "Uses" : inspection.replace(/^Has/, "Uses"), args, binding };
141
+
142
+ return { inspection, args, binding };
143
+ }
144
+
145
+ private retrieveArguments(args: string[]) {
146
+ const targetSuffix = args.find((a): a is TargetSuffix =>
147
+ SUFFIX_ARGS.has(a as TargetSuffix),
148
+ );
149
+
150
+ const matcherIndex = args.findIndex((a) => a.startsWith("With"));
151
+ const matcher =
152
+ matcherIndex !== -1
153
+ ? {
154
+ type: "with_" + args[matcherIndex].slice(4).toLowerCase(),
155
+ value: args[matcherIndex + 1],
156
+ }
157
+ : undefined;
158
+
159
+ const consumed = new Set([
160
+ ...(targetSuffix ? [targetSuffix] : []),
161
+ ...(matcherIndex !== -1
162
+ ? [args[matcherIndex], args[matcherIndex + 1]]
163
+ : []),
164
+ ]);
165
+
166
+ const resolvedArgs = args.filter((a) => !consumed.has(a));
167
+
168
+ return {
169
+ targetSuffix:
170
+ targetSuffix ??
171
+ (resolvedArgs.length > 0 ? ("named" as TargetSuffix) : undefined),
172
+ matcher,
173
+ resolvedArgs,
174
+ };
175
+ }
176
+ }
177
+
178
+ const promoteBindingToTarget = (args: string[], binding: string) => ({
179
+ args: binding && binding !== "*" ? [binding, ...args] : args,
180
+ binding: "*",
181
+ });
@@ -1,83 +1,173 @@
1
- import { MulangAdapter } from "../../src/index.js";
2
- import { isYukigoPrimitive, NumberPrimitive, Otherwise } from "yukigo-ast";
3
- import { assert } from "chai";
4
-
5
- describe("Helpers Spec", () => {
6
- it("translates correctly mulang's expectations", () => {
7
- const mulangAdapter = new MulangAdapter();
8
- const mulangExpectations = `
9
- expectations:
10
- - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
11
- binding: squareList
12
- inspection: HasBinding
13
- - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
14
- binding: squareList
15
- inspection: HasLambdaExpression
16
- - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
17
- binding: square
18
- inspection: HasArithmetic
19
- - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
20
- binding: doble
21
- inspection: Not:HasBinding
22
- - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
23
- binding: square
24
- inspection: Uses:x
25
- - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
26
- binding: squareList
27
- inspection: Uses:map
28
- - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
29
- binding: squareList
30
- inspection: Not:Uses:map`;
31
-
32
- const yukigoExpectations =
33
- mulangAdapter.translateMulangExpectations(mulangExpectations);
34
- assert.deepEqual(yukigoExpectations, [
35
- {
36
- inspection: "Declares",
37
- binding: "squareList",
38
- args: [],
39
- expected: true,
40
- },
41
- {
42
- inspection: "HasLambdaExpression",
43
- binding: "squareList",
44
- args: [],
45
- expected: true,
46
- },
47
- {
48
- inspection: "HasArithmetic",
49
- binding: "square",
50
- args: [],
51
- expected: true,
52
- },
53
- {
54
- inspection: "Declares",
55
- binding: "doble",
56
- args: [],
57
- expected: false,
58
- },
59
- {
60
- inspection: "Uses",
61
- binding: "square",
62
- args: ["x"],
63
- expected: true,
64
- },
65
- {
66
- inspection: "Uses",
67
- binding: "squareList",
68
- args: ["map"],
69
- expected: true,
70
- },
71
- {
72
- inspection: "Uses",
73
- binding: "squareList",
74
- args: ["map"],
75
- expected: false,
76
- },
77
- ]);
78
- });
79
- it("detects correctly YukigoPrimitive", () => {
80
- assert.isFalse(isYukigoPrimitive(new Otherwise()));
81
- assert.isTrue(isYukigoPrimitive(new NumberPrimitive(4)));
82
- });
83
- });
1
+ import { MulangAdapter } from "../../src/index.js";
2
+ import { isYukigoPrimitive, NumberPrimitive, Otherwise } from "yukigo-ast";
3
+ import { assert } from "chai";
4
+
5
+ describe("Helpers Spec", () => {
6
+ it("translates correctly mulang's expectations", () => {
7
+ const mulangAdapter = new MulangAdapter();
8
+ const mulangExpectations = `
9
+ expectations:
10
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
11
+ binding: squareList
12
+ inspection: HasBinding
13
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
14
+ binding: squareList
15
+ inspection: HasLambdaExpression
16
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
17
+ binding: square
18
+ inspection: HasArithmetic
19
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
20
+ binding: doble
21
+ inspection: Not:HasBinding
22
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
23
+ binding: square
24
+ inspection: Uses:x
25
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
26
+ binding: squareList
27
+ inspection: Uses:map
28
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
29
+ binding: squareList
30
+ inspection: Not:Uses:map`;
31
+
32
+ const yukigoExpectations =
33
+ mulangAdapter.translateMulangExpectations(mulangExpectations);
34
+ assert.deepEqual(yukigoExpectations, [
35
+ {
36
+ inspection: "Declares",
37
+ binding: "*",
38
+ args: ["squareList"],
39
+ expected: true,
40
+ targetSuffix: "named",
41
+ },
42
+ {
43
+ inspection: "HasLambdaExpression",
44
+ binding: "squareList",
45
+ args: [],
46
+ expected: true,
47
+ },
48
+ {
49
+ inspection: "HasArithmetic",
50
+ binding: "square",
51
+ args: [],
52
+ expected: true,
53
+ },
54
+ {
55
+ inspection: "Declares",
56
+ binding: "*",
57
+ args: ["doble"],
58
+ expected: false,
59
+ targetSuffix: "named",
60
+ },
61
+ {
62
+ inspection: "Uses",
63
+ binding: "square",
64
+ args: ["x"],
65
+ expected: true,
66
+ targetSuffix: "named",
67
+ },
68
+ {
69
+ inspection: "Uses",
70
+ binding: "squareList",
71
+ args: ["map"],
72
+ expected: true,
73
+ targetSuffix: "named",
74
+ },
75
+ {
76
+ inspection: "Uses",
77
+ binding: "squareList",
78
+ args: ["map"],
79
+ expected: false,
80
+ targetSuffix: "named",
81
+ },
82
+ ]);
83
+ });
84
+ it("translates mulang's expectations with target suffixes and matchers", () => {
85
+ const mulangAdapter = new MulangAdapter();
86
+ const expectations = `
87
+ expectations:
88
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
89
+ binding: "*"
90
+ inspection: Calls:foo:except:WithNumber:4
91
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
92
+ binding: "*"
93
+ inspection: Calls:bar:like:WithAnything
94
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
95
+ binding: "*"
96
+ inspection: Calls:baz:WithTrue
97
+ - !ruby/hash:ActiveSupport::HashWithIndifferentAccess
98
+ binding: "*"
99
+ inspection: Calls:WithAnything
100
+ `;
101
+ const yukigoExpectations = mulangAdapter.translateMulangExpectations(expectations);
102
+ assert.deepEqual(yukigoExpectations, [
103
+ {
104
+ inspection: "Calls",
105
+ binding: "*",
106
+ args: ["foo"],
107
+ expected: true,
108
+ targetSuffix: "except",
109
+ matcher: {
110
+ type: "with_number",
111
+ value: "4"
112
+ }
113
+ },
114
+ {
115
+ inspection: "Calls",
116
+ binding: "*",
117
+ args: ["bar"],
118
+ expected: true,
119
+ targetSuffix: "like",
120
+ matcher: {
121
+ type: "with_anything",
122
+ value: undefined
123
+ }
124
+ },
125
+ {
126
+ inspection: "Calls",
127
+ binding: "*",
128
+ args: ["baz"],
129
+ expected: true,
130
+ targetSuffix: "named",
131
+ matcher: {
132
+ type: "with_true",
133
+ value: undefined
134
+ }
135
+ },
136
+ {
137
+ inspection: "Calls",
138
+ binding: "*",
139
+ args: [],
140
+ expected: true,
141
+ matcher: {
142
+ type: "with_anything",
143
+ value: undefined
144
+ }
145
+ }
146
+ ]);
147
+ });
148
+ it("translates correctly expectations that provide arguments in the args property instead of the inspection string", () => {
149
+ const mulangAdapter = new MulangAdapter();
150
+ const rules = [
151
+ {
152
+ binding: "cantidadRuedasMoto",
153
+ inspection: "HasUsage",
154
+ args: ["cantidadRuedasBicicleta"],
155
+ expected: true
156
+ }
157
+ ];
158
+ const yukigoExpectations = rules.map((r) => mulangAdapter.translateMulangInspection(r));
159
+ assert.deepEqual(yukigoExpectations, [
160
+ {
161
+ inspection: "Uses",
162
+ binding: "cantidadRuedasMoto",
163
+ args: ["cantidadRuedasBicicleta"],
164
+ expected: true,
165
+ targetSuffix: "named"
166
+ }
167
+ ]);
168
+ });
169
+ it("detects correctly YukigoPrimitive", () => {
170
+ assert.isFalse(isYukigoPrimitive(new Otherwise()));
171
+ assert.isTrue(isYukigoPrimitive(new NumberPrimitive(4)));
172
+ });
173
+ });