yukigo 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/.mocharc.json +3 -3
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +193 -199
  4. package/dist/analyzer/GraphBuilder.d.ts +29 -0
  5. package/dist/analyzer/GraphBuilder.js +99 -0
  6. package/dist/analyzer/index.d.ts +11 -23
  7. package/dist/analyzer/index.js +100 -58
  8. package/dist/analyzer/inspections/functional/functional.d.ts +44 -0
  9. package/dist/analyzer/inspections/functional/functional.js +149 -0
  10. package/dist/analyzer/inspections/functional/smells.d.ts +16 -0
  11. package/dist/analyzer/inspections/functional/smells.js +98 -0
  12. package/dist/analyzer/inspections/{generic.d.ts → generic/generic.d.ts} +70 -43
  13. package/dist/analyzer/inspections/generic/generic.js +604 -0
  14. package/dist/analyzer/inspections/generic/smells.d.ts +61 -0
  15. package/dist/analyzer/inspections/generic/smells.js +349 -0
  16. package/dist/analyzer/inspections/imperative/imperative.d.ts +35 -0
  17. package/dist/analyzer/inspections/imperative/imperative.js +109 -0
  18. package/dist/analyzer/inspections/imperative/smells.d.ts +16 -0
  19. package/dist/analyzer/inspections/imperative/smells.js +58 -0
  20. package/dist/analyzer/inspections/logic/logic.d.ts +32 -0
  21. package/dist/analyzer/inspections/logic/logic.js +96 -0
  22. package/dist/analyzer/inspections/logic/smells.d.ts +15 -0
  23. package/dist/analyzer/inspections/logic/smells.js +60 -0
  24. package/dist/analyzer/inspections/object/object.d.ts +88 -0
  25. package/dist/analyzer/inspections/object/object.js +319 -0
  26. package/dist/analyzer/inspections/object/smells.d.ts +30 -0
  27. package/dist/analyzer/inspections/object/smells.js +135 -0
  28. package/dist/analyzer/utils.d.ts +26 -4
  29. package/dist/analyzer/utils.js +71 -13
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +1 -0
  32. package/dist/interpreter/components/EnvBuilder.d.ts +9 -5
  33. package/dist/interpreter/components/EnvBuilder.js +100 -30
  34. package/dist/interpreter/components/Operations.d.ts +4 -4
  35. package/dist/interpreter/components/Operations.js +17 -2
  36. package/dist/interpreter/components/PatternMatcher.d.ts +47 -17
  37. package/dist/interpreter/components/PatternMatcher.js +264 -119
  38. package/dist/interpreter/components/RuntimeContext.d.ts +35 -0
  39. package/dist/interpreter/components/RuntimeContext.js +93 -0
  40. package/dist/interpreter/components/TestRunner.d.ts +18 -0
  41. package/dist/interpreter/components/TestRunner.js +103 -0
  42. package/dist/interpreter/components/Visitor.d.ts +63 -57
  43. package/dist/interpreter/components/Visitor.js +508 -173
  44. package/dist/interpreter/components/logic/LogicEngine.d.ts +29 -0
  45. package/dist/interpreter/components/logic/LogicEngine.js +259 -0
  46. package/dist/interpreter/components/logic/LogicResolver.d.ts +53 -0
  47. package/dist/interpreter/components/logic/LogicResolver.js +471 -0
  48. package/dist/interpreter/components/logic/LogicTranslator.d.ts +14 -0
  49. package/dist/interpreter/components/logic/LogicTranslator.js +99 -0
  50. package/dist/interpreter/components/runtimes/FunctionRuntime.d.ts +12 -0
  51. package/dist/interpreter/components/runtimes/FunctionRuntime.js +147 -0
  52. package/dist/interpreter/components/runtimes/LazyRuntime.d.ts +19 -0
  53. package/dist/interpreter/components/runtimes/LazyRuntime.js +269 -0
  54. package/dist/interpreter/components/runtimes/ObjectRuntime.d.ts +35 -0
  55. package/dist/interpreter/components/runtimes/ObjectRuntime.js +126 -0
  56. package/dist/interpreter/entities.d.ts +105 -0
  57. package/dist/interpreter/entities.js +96 -0
  58. package/dist/interpreter/errors.d.ts +1 -1
  59. package/dist/interpreter/index.d.ts +4 -12
  60. package/dist/interpreter/index.js +10 -13
  61. package/dist/interpreter/trampoline.d.ts +17 -0
  62. package/dist/interpreter/trampoline.js +38 -0
  63. package/dist/interpreter/utils.d.ts +4 -7
  64. package/dist/interpreter/utils.js +25 -17
  65. package/dist/tester/index.d.ts +25 -0
  66. package/dist/tester/index.js +108 -0
  67. package/dist/utils/helpers.d.ts +0 -4
  68. package/dist/utils/helpers.js +20 -24
  69. package/package.json +2 -2
  70. package/src/analyzer/GraphBuilder.ts +142 -0
  71. package/src/analyzer/index.ts +185 -132
  72. package/src/analyzer/inspections/functional/functional.ts +121 -0
  73. package/src/analyzer/inspections/functional/smells.ts +102 -0
  74. package/src/analyzer/inspections/{generic.ts → generic/generic.ts} +581 -499
  75. package/src/analyzer/inspections/generic/smells.ts +365 -0
  76. package/src/analyzer/inspections/imperative/imperative.ts +101 -0
  77. package/src/analyzer/inspections/imperative/smells.ts +54 -0
  78. package/src/analyzer/inspections/logic/logic.ts +90 -0
  79. package/src/analyzer/inspections/logic/smells.ts +54 -0
  80. package/src/analyzer/inspections/{object.ts → object/object.ts} +264 -282
  81. package/src/analyzer/inspections/object/smells.ts +144 -0
  82. package/src/analyzer/utils.ts +109 -26
  83. package/src/index.ts +3 -2
  84. package/src/interpreter/components/EnvBuilder.ts +202 -97
  85. package/src/interpreter/components/Operations.ts +99 -81
  86. package/src/interpreter/components/PatternMatcher.ts +475 -254
  87. package/src/interpreter/components/RuntimeContext.ts +119 -0
  88. package/src/interpreter/components/TestRunner.ts +151 -0
  89. package/src/interpreter/components/Visitor.ts +1065 -493
  90. package/src/interpreter/components/logic/LogicEngine.ts +519 -0
  91. package/src/interpreter/components/logic/LogicResolver.ts +858 -0
  92. package/src/interpreter/components/logic/LogicTranslator.ts +149 -0
  93. package/src/interpreter/components/runtimes/FunctionRuntime.ts +227 -0
  94. package/src/interpreter/components/runtimes/LazyRuntime.ts +334 -0
  95. package/src/interpreter/components/runtimes/ObjectRuntime.ts +224 -0
  96. package/src/interpreter/errors.ts +47 -47
  97. package/src/interpreter/index.ts +52 -59
  98. package/src/interpreter/trampoline.ts +71 -0
  99. package/src/interpreter/utils.ts +84 -79
  100. package/src/tester/index.ts +128 -0
  101. package/src/utils/helpers.ts +67 -73
  102. package/tests/analyzer/functional.spec.ts +207 -221
  103. package/tests/analyzer/generic.spec.ts +178 -100
  104. package/tests/analyzer/helpers.spec.ts +83 -83
  105. package/tests/analyzer/logic.spec.ts +237 -292
  106. package/tests/analyzer/oop.spec.ts +323 -338
  107. package/tests/analyzer/transitive.spec.ts +166 -0
  108. package/tests/interpreter/EnvBuilder.spec.ts +183 -178
  109. package/tests/interpreter/FunctionRuntime.spec.ts +223 -234
  110. package/tests/interpreter/LazyRuntime.spec.ts +225 -190
  111. package/tests/interpreter/LogicEngine.spec.ts +327 -194
  112. package/tests/interpreter/LogicSubstitution.spec.ts +80 -0
  113. package/tests/interpreter/ObjectRuntime.spec.ts +606 -0
  114. package/tests/interpreter/Operations.spec.ts +220 -220
  115. package/tests/interpreter/PatternSystem.spec.ts +213 -189
  116. package/tests/interpreter/Tests.spec.ts +122 -0
  117. package/tests/interpreter/interpreter.spec.ts +991 -937
  118. package/tests/tester/Tester.spec.ts +153 -0
  119. package/tsconfig.build.json +15 -7
  120. package/tsconfig.json +25 -17
  121. package/dist/analyzer/inspections/functional.d.ts +0 -46
  122. package/dist/analyzer/inspections/functional.js +0 -123
  123. package/dist/analyzer/inspections/generic.js +0 -427
  124. package/dist/analyzer/inspections/imperative.d.ts +0 -37
  125. package/dist/analyzer/inspections/imperative.js +0 -105
  126. package/dist/analyzer/inspections/logic.d.ts +0 -49
  127. package/dist/analyzer/inspections/logic.js +0 -140
  128. package/dist/analyzer/inspections/object.d.ts +0 -83
  129. package/dist/analyzer/inspections/object.js +0 -235
  130. package/dist/interpreter/components/FunctionRuntime.d.ts +0 -8
  131. package/dist/interpreter/components/FunctionRuntime.js +0 -52
  132. package/dist/interpreter/components/LazyRuntime.d.ts +0 -7
  133. package/dist/interpreter/components/LazyRuntime.js +0 -75
  134. package/dist/interpreter/components/LogicEngine.d.ts +0 -21
  135. package/dist/interpreter/components/LogicEngine.js +0 -152
  136. package/dist/interpreter/components/LogicResolver.d.ts +0 -11
  137. package/dist/interpreter/components/LogicResolver.js +0 -87
  138. package/src/analyzer/inspections/functional.ts +0 -159
  139. package/src/analyzer/inspections/imperative.ts +0 -129
  140. package/src/analyzer/inspections/logic.ts +0 -166
  141. package/src/interpreter/components/FunctionRuntime.ts +0 -79
  142. package/src/interpreter/components/LazyRuntime.ts +0 -97
  143. package/src/interpreter/components/LogicEngine.ts +0 -227
  144. package/src/interpreter/components/LogicResolver.ts +0 -130
@@ -1,338 +1,323 @@
1
- import { expect } from "chai";
2
- import {
3
- Class,
4
- Method,
5
- Send,
6
- Self,
7
- SymbolPrimitive,
8
- Attribute,
9
- New,
10
- Include,
11
- Interface,
12
- Object as AstObject,
13
- PrimitiveMethod,
14
- Implement,
15
- ASTNode,
16
- Equation,
17
- UnguardedBody,
18
- Sequence,
19
- NilPrimitive,
20
- Statement,
21
- Print,
22
- StringPrimitive,
23
- } from "yukigo-ast";
24
- import {
25
- DeclaresAttribute,
26
- DeclaresClass,
27
- DeclaresInterface,
28
- DeclaresMethod,
29
- DeclaresObject,
30
- DeclaresPrimitive,
31
- DeclaresSuperclass,
32
- Implements,
33
- IncludeMixin,
34
- Instantiates,
35
- UsesDynamicPolymorphism,
36
- UsesInheritance,
37
- UsesMixins,
38
- UsesObjectComposition,
39
- UsesStaticMethodOverload,
40
- UsesDynamicMethodOverload,
41
- UsesTemplateMethod,
42
- } from "../../src/analyzer/inspections/object.js";
43
- import { executeVisitor } from "../../src/analyzer/utils.js";
44
-
45
- describe("OOP Spec", () => {
46
- const createSymbol = (name: string) => new SymbolPrimitive(name);
47
- const createMethod = (
48
- name: string,
49
- equationsCount: number = 1,
50
- isAbstract: boolean = false,
51
- bodyNode?: Statement
52
- ): Method => {
53
- const identifier = createSymbol(name);
54
- const stmts = bodyNode ? [bodyNode] : [];
55
- const equations = new Array(equationsCount).fill(
56
- new Equation([], new UnguardedBody(new Sequence(stmts)))
57
- );
58
- const method = new Method(identifier, equations);
59
- method.setMetadata("isAbstract", isAbstract);
60
- return method;
61
- };
62
-
63
- const createAttribute = (
64
- name: string,
65
- expression: any = new NilPrimitive(null)
66
- ) => {
67
- return new Attribute(createSymbol(name), expression);
68
- };
69
-
70
- const createNew = (className: string) => {
71
- return new New(createSymbol(className), []);
72
- };
73
-
74
- const createClass = (
75
- name: string,
76
- stmts: Statement[] = [],
77
- extendsName?: string,
78
- implementsName?: string
79
- ): Class => {
80
- const identifier = createSymbol(name);
81
- const extendsSymbol = extendsName ? createSymbol(extendsName) : undefined;
82
- const implementsNode = implementsName
83
- ? new Implement(createSymbol(implementsName))
84
- : undefined;
85
-
86
- const expression = new Sequence(stmts);
87
-
88
- return new Class(identifier, extendsSymbol, implementsNode, expression);
89
- };
90
-
91
- const createObject = (name: string, children: ASTNode[] = []) => {
92
- const identifier = createSymbol(name);
93
- const expression = {
94
- accept: (visitor: any) => {
95
- children.forEach((child) => child.accept(visitor));
96
- },
97
- toJSON: () => ({}),
98
- } as any;
99
- return new AstObject(identifier, expression);
100
- };
101
-
102
- describe("DeclaresAttribute", () => {
103
- it("should detect if an attribute with the specific name is declared", () => {
104
- const attr = createAttribute("energy");
105
- const visitor = new DeclaresAttribute("energy");
106
- expect(executeVisitor(attr, visitor)).to.eq(true);
107
- });
108
-
109
- it("should ignore attributes with different names", () => {
110
- const attr = createAttribute("life");
111
- const visitor = new DeclaresAttribute("energy");
112
- expect(executeVisitor(attr, visitor)).to.eq(false);
113
- });
114
- });
115
-
116
- describe("DeclaresClass", () => {
117
- it("should detect specific class declaration", () => {
118
- const node = createClass("Bird");
119
- const visitor = new DeclaresClass("Bird");
120
- expect(executeVisitor(node, visitor)).to.eq(true);
121
- });
122
- });
123
-
124
- describe("DeclaresInterface", () => {
125
- it("should detect specific interface declaration", () => {
126
- const node = new Interface(createSymbol("Flyable"), [], {} as any);
127
- const visitor = new DeclaresInterface("Flyable");
128
- expect(executeVisitor(node, visitor)).to.eq(true);
129
- });
130
- });
131
-
132
- describe("DeclaresMethod", () => {
133
- it("should detect specific method declaration", () => {
134
- const node = createMethod("fly");
135
- const visitor = new DeclaresMethod("fly");
136
- expect(executeVisitor(node, visitor)).to.eq(true);
137
- });
138
- });
139
-
140
- describe("DeclaresObject", () => {
141
- it("should detect specific object declaration", () => {
142
- const node = createObject("pepita");
143
- const visitor = new DeclaresObject("pepita");
144
- expect(executeVisitor(node, visitor)).to.eq(true);
145
- });
146
- });
147
-
148
- describe("DeclaresPrimitive", () => {
149
- it("should detect primitive operator override", () => {
150
- const op: any = "==";
151
- const node = new PrimitiveMethod(op, [], undefined as any);
152
- const visitor = new DeclaresPrimitive("==");
153
- expect(executeVisitor(node, visitor)).to.eq(true);
154
- });
155
- });
156
-
157
- describe("DeclaresSuperclass", () => {
158
- it("should detect if a class extends a specific superclass", () => {
159
- const node = createClass("Sparrow", [], "Bird");
160
- const visitor = new DeclaresSuperclass("Bird");
161
- expect(executeVisitor(node, visitor)).to.eq(true);
162
- });
163
- });
164
-
165
- describe("Implements", () => {
166
- it("should detect if a class implements a specific interface", () => {
167
- const node = createClass("Pigeon", [], undefined, "Messenger");
168
- const visitor = new Implements("Messenger");
169
- expect(executeVisitor(node, visitor)).to.eq(true);
170
- });
171
- });
172
-
173
- describe("IncludeMixin", () => {
174
- it("should detect usage of a specific mixin", () => {
175
- const node = new Include(createSymbol("Walking"));
176
- const visitor = new IncludeMixin("Walking");
177
- expect(executeVisitor(node, visitor)).to.eq(true);
178
- });
179
- });
180
-
181
- describe("Instantiates", () => {
182
- it("should detect instantiation of a specific class", () => {
183
- const node = createNew("Engine");
184
- const visitor = new Instantiates("Engine");
185
- expect(executeVisitor(node, visitor)).to.eq(true);
186
- });
187
- });
188
-
189
- describe("UsesDynamicPolymorphism", () => {
190
- it("should detect when a method name is used in at least two different places (polymorphism)", () => {
191
- const method1 = createMethod("fly");
192
- const method2 = createMethod("fly");
193
-
194
- const class1 = createClass("Bird", [method1]);
195
- const class2 = createClass("Airplane", [method2]);
196
-
197
- const visitor = new UsesDynamicPolymorphism("fly");
198
-
199
- executeVisitor(class1, visitor);
200
-
201
- expect(executeVisitor(class2, visitor)).to.eq(true);
202
- });
203
-
204
- it("should not throw if only one method is found", () => {
205
- const method1 = createMethod("fly");
206
- const class1 = createClass("Bird", [method1]);
207
- const visitor = new UsesDynamicPolymorphism("fly");
208
-
209
- expect(executeVisitor(class1, visitor)).to.eq(false);
210
- });
211
- });
212
-
213
- describe("UsesInheritance", () => {
214
- it("should detect inheritance in classes", () => {
215
- const node = createClass("Child", [], "Parent");
216
- const visitor = new UsesInheritance();
217
- expect(executeVisitor(node, visitor)).to.eq(true);
218
- });
219
-
220
- it("should detect inheritance in interfaces", () => {
221
- const node = new Interface(
222
- createSymbol("IChild"),
223
- [createSymbol("IParent")],
224
- {} as any
225
- );
226
- const visitor = new UsesInheritance();
227
- expect(executeVisitor(node, visitor)).to.eq(true);
228
- });
229
-
230
- it("should ignore classes without parent", () => {
231
- const node = createClass("Orphan");
232
- const visitor = new UsesInheritance();
233
- expect(executeVisitor(node, visitor)).to.eq(false);
234
- });
235
- });
236
-
237
- describe("UsesMixins", () => {
238
- it("should detect any mixin usage", () => {
239
- const node = new Include(createSymbol("AnyMixin"));
240
- const visitor = new UsesMixins();
241
- expect(executeVisitor(node, visitor)).to.eq(true);
242
- });
243
- });
244
-
245
- describe("UsesObjectComposition", () => {
246
- it("should detect composition when an attribute is initialized with New", () => {
247
- const instantiation = createNew("Engine");
248
- const attr = createAttribute("myEngine", instantiation);
249
- const visitor = new UsesObjectComposition();
250
- expect(executeVisitor(attr, visitor)).to.eq(true);
251
- });
252
-
253
- it("should ignore attributes initialized with literals", () => {
254
- const attr = createAttribute(
255
- "energy",
256
- new StringPrimitive("Initialized with string!")
257
- );
258
- const visitor = new UsesObjectComposition();
259
- expect(executeVisitor(attr, visitor)).to.eq(false);
260
- });
261
- });
262
-
263
- describe("UsesStaticMethodOverload", () => {
264
- it("should detect two methods with the same name in the same class", () => {
265
- const m1 = createMethod("calculate");
266
- const m2 = createMethod("calculate");
267
- const classNode = createClass("Calculator", [m1, m2]);
268
-
269
- const visitor = new UsesStaticMethodOverload();
270
- expect(executeVisitor(classNode, visitor)).to.eq(true);
271
- });
272
-
273
- it("should NOT detect overload if methods are in different classes", () => {
274
- const m1 = createMethod("calculate");
275
- const m2 = createMethod("calculate");
276
-
277
- const classA = createClass("A", [m1]);
278
- const classB = createClass("B", [m2]);
279
-
280
- const visitor = new UsesStaticMethodOverload();
281
-
282
- executeVisitor(classA, visitor);
283
-
284
- expect(executeVisitor(classB, visitor)).to.eq(false);
285
- });
286
- });
287
-
288
- describe("UsesDynamicMethodOverload", () => {
289
- it("should detect a method with multiple equations (pattern matching)", () => {
290
- const method = createMethod("fibonacci", 2);
291
- const visitor = new UsesDynamicMethodOverload();
292
- expect(executeVisitor(method, visitor)).to.eq(true);
293
- });
294
-
295
- it("should ignore methods with a single equation", () => {
296
- const method = createMethod("fibonacci", 1);
297
- const visitor = new UsesDynamicMethodOverload();
298
- expect(executeVisitor(method, visitor)).to.eq(false);
299
- });
300
- });
301
-
302
- describe("UsesTemplateMethod", () => {
303
- it("should detect template method usage (call to undeclared method on self)", () => {
304
- const declaredMethod = createMethod("abstractMethod", 1, true);
305
- const sendToSelf = new Send(
306
- new Self(),
307
- createSymbol("abstractMethod"),
308
- []
309
- );
310
-
311
- const templateMethod = createMethod("process", 1, false, sendToSelf);
312
- const classNode = createClass("MyClass", [
313
- declaredMethod,
314
- templateMethod,
315
- ]);
316
-
317
- const visitor = new UsesTemplateMethod();
318
- expect(executeVisitor(classNode, visitor)).to.eq(true);
319
- });
320
-
321
- it("should NOT detect if calling a declared method on self", () => {
322
- const declaredMethod = createMethod(
323
- "declared",
324
- 1,
325
- false,
326
- new Print(new StringPrimitive("This method is Declared"))
327
- );
328
- const sendToSelf = new Send(new Self(), createSymbol("declared"), []);
329
-
330
- const method = createMethod("process", 1, false, sendToSelf);
331
-
332
- const classNode = createClass("MyClass", [declaredMethod, method]);
333
-
334
- const visitor = new UsesTemplateMethod();
335
- expect(executeVisitor(classNode, visitor)).to.eq(false);
336
- });
337
- });
338
- });
1
+ import { expect } from "chai";
2
+ import {
3
+ Class,
4
+ Method,
5
+ Send,
6
+ Self,
7
+ SymbolPrimitive,
8
+ Attribute,
9
+ New,
10
+ Interface,
11
+ Object as AstObject,
12
+ PrimitiveMethod,
13
+ Implement,
14
+ ASTNode,
15
+ Equation,
16
+ UnguardedBody,
17
+ Sequence,
18
+ NilPrimitive,
19
+ Statement,
20
+ Print,
21
+ StringPrimitive,
22
+ Variable,
23
+ } from "yukigo-ast";
24
+ import { Analyzer, InspectionRule } from "../../src/analyzer/index.js";
25
+
26
+ describe("OOP Inspections", () => {
27
+ const createSymbol = (name: string) => new SymbolPrimitive(name);
28
+ const analyzer = new Analyzer();
29
+
30
+ const runSingleRule = (
31
+ ast: any[],
32
+ inspection: string,
33
+ expected: boolean,
34
+ binding?: string,
35
+ args: string[] = []
36
+ ) => {
37
+ const rule: InspectionRule = { inspection, binding, args, expected };
38
+ const results = analyzer.analyze(ast, [rule]);
39
+ const result = results[0];
40
+ return result.passed;
41
+ };
42
+
43
+ const createMethod = (
44
+ name: string,
45
+ equationsCount: number = 1,
46
+ isAbstract: boolean = false,
47
+ bodyNode?: Statement
48
+ ): Method => {
49
+ const identifier = createSymbol(name);
50
+ const stmts = bodyNode ? [bodyNode] : [];
51
+ const equations = new Array(equationsCount).fill(
52
+ new Equation([], new UnguardedBody(new Sequence(stmts)))
53
+ );
54
+ const method = new Method(identifier, equations);
55
+ method.setMetadata("isAbstract", isAbstract);
56
+ return method;
57
+ };
58
+
59
+ const createAttribute = (
60
+ name: string,
61
+ expression: any = new NilPrimitive(null)
62
+ ) => {
63
+ return new Attribute(createSymbol(name), expression);
64
+ };
65
+
66
+ const createNew = (className: string) => {
67
+ return new New(createSymbol(className), []);
68
+ };
69
+
70
+ const createClass = (
71
+ name: string,
72
+ stmts: Statement[] = [],
73
+ extendsName?: string,
74
+ implementsName?: string,
75
+ includes?: string[]
76
+ ): Class => {
77
+ const identifier = createSymbol(name);
78
+ const extendsSymbol = extendsName ? createSymbol(extendsName) : undefined;
79
+ const implementsNode = implementsName
80
+ ? new Implement(createSymbol(implementsName))
81
+ : undefined;
82
+ const includesMixin =
83
+ includes && includes.length > 0
84
+ ? includes.map((sym) => createSymbol(sym))
85
+ : undefined;
86
+
87
+ const expression = new Sequence(stmts);
88
+
89
+ return new Class(
90
+ identifier,
91
+ extendsSymbol,
92
+ implementsNode,
93
+ includesMixin,
94
+ expression
95
+ );
96
+ };
97
+
98
+ const createObject = (name: string, children: Statement[] = []) => {
99
+ const identifier = createSymbol(name);
100
+ const expression = new Sequence(children);
101
+ return new AstObject(identifier, expression);
102
+ };
103
+
104
+ describe("DeclaresAttribute", () => {
105
+ it("should detect if an attribute with the specific name is declared", () => {
106
+ const attr = createAttribute("energy");
107
+ const ast = [createClass("A", [attr])];
108
+ expect(
109
+ runSingleRule(ast, "DeclaresAttribute", true, undefined, ["energy"])
110
+ ).to.be.true;
111
+ });
112
+
113
+ it("should ignore attributes with different names", () => {
114
+ const attr = createAttribute("life");
115
+ const ast = [createClass("A", [attr])];
116
+ expect(
117
+ runSingleRule(ast, "DeclaresAttribute", false, undefined, ["energy"])
118
+ ).to.be.true;
119
+ });
120
+ });
121
+
122
+ describe("DeclaresClass", () => {
123
+ it("should detect specific class declaration", () => {
124
+ const ast = [createClass("Bird")];
125
+ expect(runSingleRule(ast, "DeclaresClass", true, "Bird")).to.be.true;
126
+ });
127
+ });
128
+
129
+ describe("DeclaresInterface", () => {
130
+ it("should detect specific interface declaration", () => {
131
+ const ast = [
132
+ new Interface(createSymbol("Flyable"), [], new Sequence([])),
133
+ ];
134
+ expect(runSingleRule(ast, "DeclaresInterface", true, "Flyable")).to.be
135
+ .true;
136
+ });
137
+ });
138
+
139
+ describe("DeclaresMethod", () => {
140
+ it("should detect specific method declaration", () => {
141
+ const ast = [createMethod("fly")];
142
+ expect(runSingleRule(ast, "DeclaresMethod", true, "fly")).to.be.true;
143
+ });
144
+ });
145
+
146
+ describe("DeclaresObject", () => {
147
+ it("should detect specific object declaration", () => {
148
+ const ast = [createObject("pepita")];
149
+ expect(runSingleRule(ast, "DeclaresObject", true, "pepita")).to.be.true;
150
+ });
151
+ });
152
+
153
+ describe("DeclaresPrimitive", () => {
154
+ it("should detect primitive operator override", () => {
155
+ const op: any = "==";
156
+ const ast = [new PrimitiveMethod(op, [], undefined)];
157
+ expect(runSingleRule(ast, "DeclaresPrimitive", true, undefined, ["=="]))
158
+ .to.be.true;
159
+ });
160
+ });
161
+
162
+ describe("DeclaresSuperclass", () => {
163
+ it("should detect if a class extends a specific superclass", () => {
164
+ const ast = [createClass("Sparrow", [], "Bird")];
165
+ expect(
166
+ runSingleRule(ast, "DeclaresSuperclass", true, undefined, ["Bird"])
167
+ ).to.be.true;
168
+ });
169
+ });
170
+
171
+ describe("Implements", () => {
172
+ it("should detect if a class implements a specific interface", () => {
173
+ const ast = [createClass("Pigeon", [], undefined, "Messenger")];
174
+ expect(runSingleRule(ast, "Implements", true, undefined, ["Messenger"]))
175
+ .to.be.true;
176
+ });
177
+ });
178
+
179
+ describe("IncludeMixin", () => {
180
+ it("should detect usage of a specific mixin", () => {
181
+ const ast = [
182
+ createClass("ClassWithMixin", [], undefined, undefined, ["Walking"]),
183
+ ];
184
+ expect(runSingleRule(ast, "IncludeMixin", true, undefined, ["Walking"]))
185
+ .to.be.true;
186
+ });
187
+ });
188
+
189
+ describe("Instantiates", () => {
190
+ it("should detect instantiation of a specific class", () => {
191
+ const ast = [new Variable(new SymbolPrimitive("a"), createNew("Engine"))];
192
+ expect(runSingleRule(ast, "Instantiates", true, undefined, ["Engine"])).to
193
+ .be.true;
194
+ });
195
+ });
196
+
197
+ describe("UsesDynamicPolymorphism", () => {
198
+ it("should detect when a method name is used in at least two different places (polymorphism)", () => {
199
+ const method1 = createMethod("fly");
200
+ const method2 = createMethod("fly");
201
+ const class1 = createClass("Bird", [method1]);
202
+ const class2 = createClass("Airplane", [method2]);
203
+ const ast = [class1, class2];
204
+ expect(runSingleRule(ast, "UsesDynamicPolymorphism", true, undefined, ["fly"]))
205
+ .to.be.true;
206
+ });
207
+
208
+ it("should not throw if only one method is found", () => {
209
+ const method1 = createMethod("fly");
210
+ const class1 = createClass("Bird", [method1]);
211
+ const ast = [class1];
212
+ expect(runSingleRule(ast, "UsesDynamicPolymorphism", false, "fly")).to.be
213
+ .true;
214
+ });
215
+ });
216
+
217
+ describe("UsesInheritance", () => {
218
+ it("should detect inheritance in classes", () => {
219
+ const ast = [createClass("Child", [], "Parent")];
220
+ expect(runSingleRule(ast, "UsesInheritance", true)).to.be.true;
221
+ });
222
+
223
+ it("should detect inheritance in interfaces", () => {
224
+ const ast = [
225
+ new Interface(
226
+ createSymbol("IChild"),
227
+ [createSymbol("IParent")],
228
+ new Sequence([])
229
+ ),
230
+ ];
231
+ expect(runSingleRule(ast, "UsesInheritance", true)).to.be.true;
232
+ });
233
+
234
+ it("should ignore classes without parent", () => {
235
+ const ast = [createClass("Orphan")];
236
+ expect(runSingleRule(ast, "UsesInheritance", false)).to.be.true;
237
+ });
238
+ });
239
+
240
+ describe("UsesMixins", () => {
241
+ it("should detect any mixin usage", () => {
242
+ const ast = [
243
+ createClass("ClassWithMixin", [], undefined, undefined, ["AnyMixin"]),
244
+ ];
245
+ expect(runSingleRule(ast, "UsesMixins", true)).to.be.true;
246
+ });
247
+ });
248
+
249
+ describe("UsesObjectComposition", () => {
250
+ it("should detect composition when an attribute is initialized with New", () => {
251
+ const instantiation = createNew("Engine");
252
+ const attr = createAttribute("myEngine", instantiation);
253
+ const ast = [createClass("A", [attr])];
254
+ expect(runSingleRule(ast, "UsesObjectComposition", true)).to.be.true;
255
+ });
256
+
257
+ it("should ignore attributes initialized with literals", () => {
258
+ const attr = createAttribute(
259
+ "energy",
260
+ new StringPrimitive("Initialized with string!")
261
+ );
262
+ const ast = [createClass("A", [attr])];
263
+ expect(runSingleRule(ast, "UsesObjectComposition", false)).to.be.true;
264
+ });
265
+ });
266
+
267
+ describe("UsesStaticMethodOverload", () => {
268
+ it("should detect two methods with the same name in the same class", () => {
269
+ const m1 = createMethod("calculate");
270
+ const m2 = createMethod("calculate");
271
+ const ast = [createClass("Calculator", [m1, m2])];
272
+ expect(runSingleRule(ast, "UsesStaticMethodOverload", true)).to.be.true;
273
+ });
274
+
275
+ it("should NOT detect overload if methods are in different classes", () => {
276
+ const m1 = createMethod("calculate");
277
+ const m2 = createMethod("calculate");
278
+ const classA = createClass("A", [m1]);
279
+ const classB = createClass("B", [m2]);
280
+ const ast = [classA, classB];
281
+ expect(runSingleRule(ast, "UsesStaticMethodOverload", false)).to.be.true;
282
+ });
283
+ });
284
+
285
+ describe("UsesDynamicMethodOverload", () => {
286
+ it("should detect a method with multiple equations (pattern matching)", () => {
287
+ const ast = [createMethod("fibonacci", 2)];
288
+ expect(runSingleRule(ast, "UsesDynamicMethodOverload", true)).to.be.true;
289
+ });
290
+
291
+ it("should ignore methods with a single equation", () => {
292
+ const ast = [createMethod("fibonacci", 1)];
293
+ expect(runSingleRule(ast, "UsesDynamicMethodOverload", false)).to.be.true;
294
+ });
295
+ });
296
+
297
+ describe("UsesTemplateMethod", () => {
298
+ it("should detect template method usage (call to undeclared method on self)", () => {
299
+ const declaredMethod = createMethod("abstractMethod", 1, true);
300
+ const sendToSelf = new Send(
301
+ new Self(),
302
+ createSymbol("abstractMethod"),
303
+ []
304
+ );
305
+ const templateMethod = createMethod("process", 1, false, sendToSelf);
306
+ const ast = [createClass("MyClass", [declaredMethod, templateMethod])];
307
+ expect(runSingleRule(ast, "UsesTemplateMethod", true)).to.be.true;
308
+ });
309
+
310
+ it("should NOT detect if calling a declared method on self", () => {
311
+ const declaredMethod = createMethod(
312
+ "declared",
313
+ 1,
314
+ false,
315
+ new Print(new StringPrimitive("This method is Declared"))
316
+ );
317
+ const sendToSelf = new Send(new Self(), createSymbol("declared"), []);
318
+ const method = createMethod("process", 1, false, sendToSelf);
319
+ const ast = [createClass("MyClass", [declaredMethod, method])];
320
+ expect(runSingleRule(ast, "UsesTemplateMethod", false)).to.be.true;
321
+ });
322
+ });
323
+ });