yukigo 0.3.2 → 0.3.4

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,98 @@
1
- ## 0.3.2 (2026-08-04)
1
+ ## 0.3.4 (2026-08-05)
2
2
 
3
3
  ### 🩹 Fixes
4
4
 
5
+ - **yukigo:** Fix MulangAdapter target suffix and matcher ([4d0a988](https://github.com/miyukiproject/yukigo/commit/4d0a988))
5
6
  - **yukigo:** fix v0 Mulang Inspections translation ([2995ccc](https://github.com/miyukiproject/yukigo/commit/2995ccc))
6
7
 
7
8
  ### 🧱 Updated Dependencies
8
9
 
9
- - Updated yukigo-ast to 0.3.2
10
+ - Updated yukigo-ast to 0.3.4
10
11
 
11
12
  ### ❤️ Thank You
12
13
 
13
14
  - noiseArch
14
15
 
15
- ## 0.3.1 (2026-08-01)
16
-
17
- ### 🧱 Updated Dependencies
18
-
19
- - Updated yukigo-ast to 0.3.1
20
-
21
- ## 0.3.0 (2026-08-01)
22
-
23
- ### 🚀 Features
24
-
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))
16
+ ## 0.3.3 (2026-08-05)
30
17
 
31
18
  ### 🩹 Fixes
32
19
 
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))
20
+ - **yukigo:** Fix MulangAdapter target suffix and matcher ([4d0a988](https://github.com/miyukiproject/yukigo/commit/4d0a988))
21
+ - **yukigo:** fix v0 Mulang Inspections translation ([2995ccc](https://github.com/miyukiproject/yukigo/commit/2995ccc))
57
22
 
58
23
  ### 🧱 Updated Dependencies
59
24
 
60
- - Updated yukigo-ast to 0.3.0
25
+ - Updated yukigo-ast to 0.3.3
61
26
 
62
27
  ### ❤️ Thank You
63
28
 
64
29
  - noiseArch
65
30
 
31
+ ## 0.3.2 (2026-08-05)
32
+
33
+ ### 🩹 Fixes
34
+
35
+ - **yukigo:** fix v0 Mulang Inspections translation ([2995ccc](https://github.com/miyukiproject/yukigo/commit/2995ccc))
36
+
37
+ ### 🧱 Updated Dependencies
38
+
39
+ - Updated yukigo-ast to 0.3.2
40
+
41
+ ### ❤️ Thank You
42
+
43
+ - noiseArch
44
+
45
+ ## 0.3.1 (2026-08-01)
46
+
47
+ ### 🧱 Updated Dependencies
48
+
49
+ - Updated yukigo-ast to 0.3.1
50
+
51
+ ## 0.3.0 (2026-08-01)
52
+
53
+ ### 🚀 Features
54
+
55
+ - Interpreter NativeBody support & YukigoHooks ([0b854b4](https://github.com/miyukiproject/yukigo/commit/0b854b4))
56
+ - **interpreter:** implement semantic error handling and stack traces ([b61768f](https://github.com/miyukiproject/yukigo/commit/b61768f))
57
+ - **interpreter:** integrate Kernel as the primary execution engine ([503af3d](https://github.com/miyukiproject/yukigo/commit/503af3d))
58
+ - **interpreter:** introduce YukigoKernel and command-based infrastructure ([9380892](https://github.com/miyukiproject/yukigo/commit/9380892))
59
+ - **yukigo:** implement and test YukigoKernel ([3d42ac4](https://github.com/miyukiproject/yukigo/commit/3d42ac4))
60
+
61
+ ### 🩹 Fixes
62
+
63
+ - **yukigo-e2e:** fixes to pass E2E tests ([02d895f](https://github.com/miyukiproject/yukigo/commit/02d895f))
64
+ - **yukigo:** allow concatenation with Plus operator ([7799416](https://github.com/miyukiproject/yukigo/commit/7799416))
65
+ - **yukigo:** fix circular dependency ([64534a8](https://github.com/miyukiproject/yukigo/commit/64534a8))
66
+ - **yukigo:** failed test didnt throw bc now it handles 1 + "a" ([23ebeb6](https://github.com/miyukiproject/yukigo/commit/23ebeb6))
67
+ - **yukigo:** zero-arity functions and Wollok bugs ([995dad4](https://github.com/miyukiproject/yukigo/commit/995dad4))
68
+ - **yukigo:** ObjectRuntime error should return RaiseCommand not FailCommand ([ccb31fa](https://github.com/miyukiproject/yukigo/commit/ccb31fa))
69
+ - **yukigo:** method registration with arity to allow overload ([8ffcb36](https://github.com/miyukiproject/yukigo/commit/8ffcb36))
70
+ - TypeSignature is not a Binding ([13c2c5a](https://github.com/miyukiproject/yukigo/commit/13c2c5a))
71
+ - **yukigo:** support If and Call nodes in LogicEngine ([a0f4086](https://github.com/miyukiproject/yukigo/commit/a0f4086))
72
+ - **yukigo:** fixed functional tester was evaluating logic tests ([914310e](https://github.com/miyukiproject/yukigo/commit/914310e))
73
+ - **yukigo:** solved scope leak at logical binary operations ([8779899](https://github.com/miyukiproject/yukigo/commit/8779899))
74
+ - ToString operator ([f5284ab](https://github.com/miyukiproject/yukigo/commit/f5284ab))
75
+ - **yukigo:** fix pattern matching in FunctionRuntime ([467cdf7](https://github.com/miyukiproject/yukigo/commit/467cdf7))
76
+ - **yukigo:** fix bitwise negation ([b13ef4d](https://github.com/miyukiproject/yukigo/commit/b13ef4d))
77
+ - **yukigo:** fix asynchronous logic unification and parameter resolution ([e467068](https://github.com/miyukiproject/yukigo/commit/e467068))
78
+ - **yukigo:** arreglo operaciones y sustitucion logica ([c95f33f](https://github.com/miyukiproject/yukigo/commit/c95f33f))
79
+ - fix parsers grammar compilation to export to .cjs ([ef59727](https://github.com/miyukiproject/yukigo/commit/ef59727))
80
+ - **yukigo:** fix tests to reflect correct representations ([76b1512](https://github.com/miyukiproject/yukigo/commit/76b1512))
81
+ - **yukigo:** fix Visitor and LazyRuntime to use new correct methods from context ([0eb9168](https://github.com/miyukiproject/yukigo/commit/0eb9168))
82
+ - **yukigo:** arreglos en logicengine ([0604555](https://github.com/miyukiproject/yukigo/commit/0604555))
83
+ - **yukigo:** fix mini error in oop test ([b9fd07e](https://github.com/miyukiproject/yukigo/commit/b9fd07e))
84
+ - **yukigo:** removed unwanted error handling ([0d1ff15](https://github.com/miyukiproject/yukigo/commit/0d1ff15))
85
+ - **yukigo:** replace instanceof with .is method in analyzer ([d953413](https://github.com/miyukiproject/yukigo/commit/d953413))
86
+ - **yukigo:** add InterpreterConfig as export ([280146c](https://github.com/miyukiproject/yukigo/commit/280146c))
87
+
88
+ ### 🧱 Updated Dependencies
89
+
90
+ - Updated yukigo-ast to 0.3.0
91
+
92
+ ### ❤️ Thank You
93
+
94
+ - noiseArch
95
+
66
96
  ## 0.2.3 (2026-05-02)
67
97
 
68
98
  ### 🩹 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.4",
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
+ });