yukigo 0.2.2 → 0.3.1

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 (266) hide show
  1. package/.mocharc.json +3 -3
  2. package/CHANGELOG.md +102 -41
  3. package/README.md +193 -193
  4. package/dist/analyzer/GraphBuilder.d.ts +1 -0
  5. package/dist/analyzer/GraphBuilder.js +2 -1
  6. package/dist/analyzer/index.d.ts +0 -7
  7. package/dist/analyzer/index.js +3 -3
  8. package/dist/analyzer/inspections/functional/functional.d.ts +1 -1
  9. package/dist/analyzer/inspections/functional/functional.js +1 -1
  10. package/dist/analyzer/inspections/functional/smells.d.ts +3 -3
  11. package/dist/analyzer/inspections/functional/smells.js +27 -35
  12. package/dist/analyzer/inspections/generic/generic.d.ts +21 -14
  13. package/dist/analyzer/inspections/generic/generic.js +78 -59
  14. package/dist/analyzer/inspections/generic/smells.d.ts +2 -0
  15. package/dist/analyzer/inspections/generic/smells.js +55 -64
  16. package/dist/analyzer/inspections/imperative/imperative.d.ts +3 -3
  17. package/dist/analyzer/inspections/imperative/imperative.js +3 -3
  18. package/dist/analyzer/inspections/imperative/smells.d.ts +1 -0
  19. package/dist/analyzer/inspections/imperative/smells.js +8 -5
  20. package/dist/analyzer/inspections/logic/logic.d.ts +8 -8
  21. package/dist/analyzer/inspections/logic/logic.js +5 -5
  22. package/dist/analyzer/inspections/logic/smells.js +2 -2
  23. package/dist/analyzer/inspections/object/object.d.ts +5 -5
  24. package/dist/analyzer/inspections/object/object.js +22 -21
  25. package/dist/analyzer/inspections/object/smells.js +11 -23
  26. package/dist/analyzer/utils.d.ts +6 -2
  27. package/dist/analyzer/utils.js +6 -2
  28. package/dist/interpreter/components/EnvBuilder.d.ts +20 -1
  29. package/dist/interpreter/components/EnvBuilder.js +211 -66
  30. package/dist/interpreter/components/EqualityComparer.d.ts +15 -0
  31. package/dist/interpreter/components/EqualityComparer.js +69 -0
  32. package/dist/interpreter/components/Operations.d.ts +17 -14
  33. package/dist/interpreter/components/Operations.js +61 -62
  34. package/dist/interpreter/components/PatternMatcher.d.ts +19 -58
  35. package/dist/interpreter/components/PatternMatcher.js +185 -286
  36. package/dist/interpreter/components/RuntimeContext.d.ts +24 -15
  37. package/dist/interpreter/components/RuntimeContext.js +43 -4
  38. package/dist/interpreter/components/TestRunner.d.ts +15 -13
  39. package/dist/interpreter/components/TestRunner.js +86 -53
  40. package/dist/interpreter/components/evaluators/BaseEvaluator.d.ts +19 -0
  41. package/dist/interpreter/components/evaluators/BaseEvaluator.js +20 -0
  42. package/dist/interpreter/components/evaluators/ExpressionEvaluator.d.ts +34 -0
  43. package/dist/interpreter/components/evaluators/ExpressionEvaluator.js +298 -0
  44. package/dist/interpreter/components/evaluators/OperationEvaluator.d.ts +25 -0
  45. package/dist/interpreter/components/evaluators/OperationEvaluator.js +226 -0
  46. package/dist/interpreter/components/evaluators/PrimitiveEvaluator.d.ts +19 -0
  47. package/dist/interpreter/components/evaluators/PrimitiveEvaluator.js +50 -0
  48. package/dist/interpreter/components/evaluators/StatementEvaluator.d.ts +28 -0
  49. package/dist/interpreter/components/evaluators/StatementEvaluator.js +240 -0
  50. package/dist/interpreter/components/evaluators/TestingEvaluator.d.ts +15 -0
  51. package/dist/interpreter/components/evaluators/TestingEvaluator.js +14 -0
  52. package/dist/interpreter/components/evaluators/index.d.ts +110 -0
  53. package/dist/interpreter/components/evaluators/index.js +21 -0
  54. package/dist/interpreter/components/evaluators/utils.d.ts +7 -0
  55. package/dist/interpreter/components/evaluators/utils.js +50 -0
  56. package/dist/interpreter/components/hooks/YukigoHook.d.ts +27 -0
  57. package/dist/interpreter/components/hooks/YukigoHook.js +1 -0
  58. package/dist/interpreter/components/kernel/commands.d.ts +122 -0
  59. package/dist/interpreter/components/kernel/commands.js +203 -0
  60. package/dist/interpreter/components/kernel/index.d.ts +51 -0
  61. package/dist/interpreter/components/kernel/index.js +176 -0
  62. package/dist/interpreter/components/logic/LogicEngine.d.ts +26 -23
  63. package/dist/interpreter/components/logic/LogicEngine.js +234 -221
  64. package/dist/interpreter/components/logic/LogicResolver.d.ts +22 -51
  65. package/dist/interpreter/components/logic/LogicResolver.js +73 -440
  66. package/dist/interpreter/components/logic/LogicTerm.d.ts +104 -0
  67. package/dist/interpreter/components/logic/LogicTerm.js +423 -0
  68. package/dist/interpreter/components/logic/LogicTranslator.d.ts +25 -9
  69. package/dist/interpreter/components/logic/LogicTranslator.js +211 -74
  70. package/dist/interpreter/components/runtimes/FunctionRuntime.d.ts +6 -4
  71. package/dist/interpreter/components/runtimes/FunctionRuntime.js +82 -84
  72. package/dist/interpreter/components/runtimes/LazyRuntime.d.ts +13 -14
  73. package/dist/interpreter/components/runtimes/LazyRuntime.js +112 -239
  74. package/dist/interpreter/components/runtimes/ObjectRuntime.d.ts +17 -14
  75. package/dist/interpreter/components/runtimes/ObjectRuntime.js +105 -74
  76. package/dist/interpreter/errors.d.ts +10 -0
  77. package/dist/interpreter/errors.js +17 -0
  78. package/dist/interpreter/index.d.ts +9 -5
  79. package/dist/interpreter/index.js +11 -13
  80. package/dist/interpreter/primitives/YuValue.d.ts +21 -0
  81. package/dist/interpreter/primitives/YuValue.js +40 -0
  82. package/dist/interpreter/primitives/capabilities.d.ts +90 -0
  83. package/dist/interpreter/primitives/capabilities.js +1 -0
  84. package/dist/interpreter/primitives/entities/LazyList.d.ts +48 -0
  85. package/dist/interpreter/primitives/entities/LazyList.js +164 -0
  86. package/dist/interpreter/primitives/entities/LogicResult.d.ts +51 -0
  87. package/dist/interpreter/primitives/entities/LogicResult.js +101 -0
  88. package/dist/interpreter/primitives/entities/RuntimeClass.d.ts +24 -0
  89. package/dist/interpreter/primitives/entities/RuntimeClass.js +57 -0
  90. package/dist/interpreter/primitives/entities/RuntimeFunction.d.ts +40 -0
  91. package/dist/interpreter/primitives/entities/RuntimeFunction.js +73 -0
  92. package/dist/interpreter/primitives/entities/RuntimeObject.d.ts +27 -0
  93. package/dist/interpreter/primitives/entities/RuntimeObject.js +82 -0
  94. package/dist/interpreter/primitives/entities/RuntimePredicate.d.ts +19 -0
  95. package/dist/interpreter/primitives/entities/RuntimePredicate.js +59 -0
  96. package/dist/interpreter/primitives/index.d.ts +14 -0
  97. package/dist/interpreter/primitives/index.js +14 -0
  98. package/dist/interpreter/primitives/scalars/YuBoolean.d.ts +24 -0
  99. package/dist/interpreter/primitives/scalars/YuBoolean.js +69 -0
  100. package/dist/interpreter/primitives/scalars/YuChar.d.ts +28 -0
  101. package/dist/interpreter/primitives/scalars/YuChar.js +78 -0
  102. package/dist/interpreter/primitives/scalars/YuNil.d.ts +25 -0
  103. package/dist/interpreter/primitives/scalars/YuNil.js +73 -0
  104. package/dist/interpreter/primitives/scalars/YuNumber.d.ts +49 -0
  105. package/dist/interpreter/primitives/scalars/YuNumber.js +165 -0
  106. package/dist/interpreter/primitives/sequences/YuArray.d.ts +33 -0
  107. package/dist/interpreter/primitives/sequences/YuArray.js +150 -0
  108. package/dist/interpreter/primitives/sequences/YuString.d.ts +40 -0
  109. package/dist/interpreter/primitives/sequences/YuString.js +130 -0
  110. package/dist/interpreter/utils.d.ts +23 -10
  111. package/dist/interpreter/utils.js +25 -30
  112. package/dist/tester/index.d.ts +3 -3
  113. package/dist/tester/index.js +39 -28
  114. package/dist/utils/helpers.d.ts +12 -5
  115. package/dist/utils/helpers.js +6 -79
  116. package/package.json +3 -16
  117. package/src/analyzer/GraphBuilder.ts +143 -142
  118. package/src/analyzer/index.ts +185 -194
  119. package/src/analyzer/inspections/functional/functional.ts +121 -121
  120. package/src/analyzer/inspections/functional/smells.ts +88 -102
  121. package/src/analyzer/inspections/generic/generic.ts +610 -581
  122. package/src/analyzer/inspections/generic/smells.ts +355 -365
  123. package/src/analyzer/inspections/imperative/imperative.ts +104 -101
  124. package/src/analyzer/inspections/imperative/smells.ts +58 -54
  125. package/src/analyzer/inspections/logic/logic.ts +84 -90
  126. package/src/analyzer/inspections/logic/smells.ts +54 -54
  127. package/src/analyzer/inspections/object/object.ts +295 -264
  128. package/src/analyzer/inspections/object/smells.ts +130 -144
  129. package/src/analyzer/utils.ts +115 -109
  130. package/src/index.ts +3 -3
  131. package/src/interpreter/components/EnvBuilder.ts +405 -202
  132. package/src/interpreter/components/EqualityComparer.ts +107 -0
  133. package/src/interpreter/components/Operations.ts +126 -99
  134. package/src/interpreter/components/PatternMatcher.ts +375 -475
  135. package/src/interpreter/components/RuntimeContext.ts +181 -119
  136. package/src/interpreter/components/TestRunner.ts +223 -151
  137. package/src/interpreter/components/evaluators/BaseEvaluator.ts +36 -0
  138. package/src/interpreter/components/evaluators/ExpressionEvaluator.ts +475 -0
  139. package/src/interpreter/components/evaluators/OperationEvaluator.ts +477 -0
  140. package/src/interpreter/components/evaluators/PrimitiveEvaluator.ts +80 -0
  141. package/src/interpreter/components/evaluators/StatementEvaluator.ts +372 -0
  142. package/src/interpreter/components/evaluators/TestingEvaluator.ts +26 -0
  143. package/src/interpreter/components/evaluators/index.ts +34 -0
  144. package/src/interpreter/components/evaluators/utils.ts +64 -0
  145. package/src/interpreter/components/hooks/YukigoHook.ts +45 -0
  146. package/src/interpreter/components/kernel/commands.ts +255 -0
  147. package/src/interpreter/components/kernel/index.ts +222 -0
  148. package/src/interpreter/components/logic/LogicEngine.ts +509 -519
  149. package/src/interpreter/components/logic/LogicResolver.ts +209 -858
  150. package/src/interpreter/components/logic/LogicTerm.ts +477 -0
  151. package/src/interpreter/components/logic/LogicTranslator.ts +362 -149
  152. package/src/interpreter/components/runtimes/FunctionRuntime.ts +249 -227
  153. package/src/interpreter/components/runtimes/LazyRuntime.ts +226 -334
  154. package/src/interpreter/components/runtimes/ObjectRuntime.ts +262 -224
  155. package/src/interpreter/errors.ts +68 -47
  156. package/src/interpreter/index.ts +50 -52
  157. package/src/interpreter/primitives/YuValue.ts +64 -0
  158. package/src/interpreter/primitives/capabilities.ts +111 -0
  159. package/src/interpreter/primitives/entities/LazyList.ts +225 -0
  160. package/src/interpreter/primitives/entities/LogicResult.ts +133 -0
  161. package/src/interpreter/primitives/entities/RuntimeClass.ts +70 -0
  162. package/src/interpreter/primitives/entities/RuntimeFunction.ts +108 -0
  163. package/src/interpreter/primitives/entities/RuntimeObject.ts +110 -0
  164. package/src/interpreter/primitives/entities/RuntimePredicate.ts +77 -0
  165. package/src/interpreter/primitives/index.ts +17 -0
  166. package/src/interpreter/primitives/scalars/YuBoolean.ts +79 -0
  167. package/src/interpreter/primitives/scalars/YuChar.ts +94 -0
  168. package/src/interpreter/primitives/scalars/YuNil.ts +84 -0
  169. package/src/interpreter/primitives/scalars/YuNumber.ts +187 -0
  170. package/src/interpreter/primitives/sequences/YuArray.ts +178 -0
  171. package/src/interpreter/primitives/sequences/YuString.ts +165 -0
  172. package/src/interpreter/utils.ts +105 -84
  173. package/src/tester/index.ts +157 -128
  174. package/src/utils/helpers.ts +81 -168
  175. package/tests/analyzer/functional.spec.ts +221 -207
  176. package/tests/analyzer/generic.spec.ts +178 -178
  177. package/tests/analyzer/helpers.spec.ts +83 -89
  178. package/tests/analyzer/logic.spec.ts +237 -237
  179. package/tests/analyzer/oop.spec.ts +320 -323
  180. package/tests/analyzer/transitive.spec.ts +166 -166
  181. package/tests/interpreter/EnvBuilder.spec.ts +181 -183
  182. package/tests/interpreter/FunctionRuntime.spec.ts +224 -223
  183. package/tests/interpreter/LazyRuntime.spec.ts +227 -225
  184. package/tests/interpreter/LogicEngine.spec.ts +371 -327
  185. package/tests/interpreter/LogicSubstitution.spec.ts +90 -80
  186. package/tests/interpreter/ObjectRuntime.spec.ts +431 -606
  187. package/tests/interpreter/Operations.spec.ts +312 -220
  188. package/tests/interpreter/PatternSystem.spec.ts +254 -213
  189. package/tests/interpreter/Tests.spec.ts +122 -122
  190. package/tests/interpreter/interpreter.spec.ts +1143 -991
  191. package/tests/kernel/YukigoKernel.spec.ts +112 -0
  192. package/tests/tester/Tester.spec.ts +188 -152
  193. package/tsconfig.build.json +16 -15
  194. package/tsconfig.json +27 -25
  195. package/dist/interpreter/components/Visitor.d.ts +0 -70
  196. package/dist/interpreter/components/Visitor.js +0 -634
  197. package/dist/interpreter/entities.d.ts +0 -105
  198. package/dist/interpreter/entities.js +0 -96
  199. package/dist/interpreter/trampoline.d.ts +0 -17
  200. package/dist/interpreter/trampoline.js +0 -38
  201. package/dist/src/analyzer/GraphBuilder.d.ts +0 -30
  202. package/dist/src/analyzer/GraphBuilder.js +0 -100
  203. package/dist/src/analyzer/index.d.ts +0 -59
  204. package/dist/src/analyzer/index.js +0 -152
  205. package/dist/src/analyzer/inspections/functional/functional.d.ts +0 -44
  206. package/dist/src/analyzer/inspections/functional/functional.js +0 -149
  207. package/dist/src/analyzer/inspections/functional/smells.d.ts +0 -16
  208. package/dist/src/analyzer/inspections/functional/smells.js +0 -98
  209. package/dist/src/analyzer/inspections/generic/generic.d.ts +0 -178
  210. package/dist/src/analyzer/inspections/generic/generic.js +0 -604
  211. package/dist/src/analyzer/inspections/generic/smells.d.ts +0 -61
  212. package/dist/src/analyzer/inspections/generic/smells.js +0 -349
  213. package/dist/src/analyzer/inspections/imperative/imperative.d.ts +0 -35
  214. package/dist/src/analyzer/inspections/imperative/imperative.js +0 -109
  215. package/dist/src/analyzer/inspections/imperative/smells.d.ts +0 -16
  216. package/dist/src/analyzer/inspections/imperative/smells.js +0 -58
  217. package/dist/src/analyzer/inspections/logic/logic.d.ts +0 -32
  218. package/dist/src/analyzer/inspections/logic/logic.js +0 -96
  219. package/dist/src/analyzer/inspections/logic/smells.d.ts +0 -15
  220. package/dist/src/analyzer/inspections/logic/smells.js +0 -60
  221. package/dist/src/analyzer/inspections/object/object.d.ts +0 -90
  222. package/dist/src/analyzer/inspections/object/object.js +0 -321
  223. package/dist/src/analyzer/inspections/object/smells.d.ts +0 -30
  224. package/dist/src/analyzer/inspections/object/smells.js +0 -135
  225. package/dist/src/analyzer/utils.d.ts +0 -30
  226. package/dist/src/analyzer/utils.js +0 -78
  227. package/dist/src/index.d.ts +0 -4
  228. package/dist/src/index.js +0 -4
  229. package/dist/src/interpreter/components/EnvBuilder.d.ts +0 -21
  230. package/dist/src/interpreter/components/EnvBuilder.js +0 -155
  231. package/dist/src/interpreter/components/Operations.d.ts +0 -14
  232. package/dist/src/interpreter/components/Operations.js +0 -84
  233. package/dist/src/interpreter/components/PatternMatcher.d.ts +0 -73
  234. package/dist/src/interpreter/components/PatternMatcher.js +0 -358
  235. package/dist/src/interpreter/components/RuntimeContext.d.ts +0 -35
  236. package/dist/src/interpreter/components/RuntimeContext.js +0 -93
  237. package/dist/src/interpreter/components/TestRunner.d.ts +0 -19
  238. package/dist/src/interpreter/components/TestRunner.js +0 -110
  239. package/dist/src/interpreter/components/Visitor.d.ts +0 -71
  240. package/dist/src/interpreter/components/Visitor.js +0 -638
  241. package/dist/src/interpreter/components/logic/LogicEngine.d.ts +0 -29
  242. package/dist/src/interpreter/components/logic/LogicEngine.js +0 -259
  243. package/dist/src/interpreter/components/logic/LogicResolver.d.ts +0 -53
  244. package/dist/src/interpreter/components/logic/LogicResolver.js +0 -484
  245. package/dist/src/interpreter/components/logic/LogicTranslator.d.ts +0 -14
  246. package/dist/src/interpreter/components/logic/LogicTranslator.js +0 -99
  247. package/dist/src/interpreter/components/runtimes/FunctionRuntime.d.ts +0 -12
  248. package/dist/src/interpreter/components/runtimes/FunctionRuntime.js +0 -149
  249. package/dist/src/interpreter/components/runtimes/LazyRuntime.d.ts +0 -19
  250. package/dist/src/interpreter/components/runtimes/LazyRuntime.js +0 -269
  251. package/dist/src/interpreter/components/runtimes/ObjectRuntime.d.ts +0 -35
  252. package/dist/src/interpreter/components/runtimes/ObjectRuntime.js +0 -126
  253. package/dist/src/interpreter/errors.d.ts +0 -19
  254. package/dist/src/interpreter/errors.js +0 -36
  255. package/dist/src/interpreter/index.d.ts +0 -24
  256. package/dist/src/interpreter/index.js +0 -41
  257. package/dist/src/interpreter/trampoline.d.ts +0 -17
  258. package/dist/src/interpreter/trampoline.js +0 -38
  259. package/dist/src/interpreter/utils.d.ts +0 -11
  260. package/dist/src/interpreter/utils.js +0 -65
  261. package/dist/src/tester/index.d.ts +0 -25
  262. package/dist/src/tester/index.js +0 -113
  263. package/dist/src/utils/helpers.d.ts +0 -13
  264. package/dist/src/utils/helpers.js +0 -52
  265. package/src/interpreter/components/Visitor.ts +0 -1065
  266. package/src/interpreter/trampoline.ts +0 -71
@@ -1,365 +1,355 @@
1
- import {
2
- Application,
3
- ASTNode,
4
- BooleanPrimitive,
5
- Call,
6
- Catch,
7
- Equation,
8
- Expression,
9
- Function,
10
- If,
11
- LogicalBinaryOperation,
12
- NilPrimitive,
13
- Print,
14
- Return,
15
- Sequence,
16
- StopTraversalException,
17
- SymbolPrimitive,
18
- Variable,
19
- VariablePattern,
20
- } from "yukigo-ast";
21
- import { AutoScoped, ScopedVisitor, VisitorConstructor } from "../../utils.js";
22
-
23
- function isSequenceEmpty(node: Expression): boolean {
24
- return node instanceof Sequence && node.statements.length === 0;
25
- }
26
-
27
- /**
28
- * Basic Levenshtein distance for typo detection
29
- */
30
- function getLevenshteinDistance(a: string, b: string): number {
31
- const matrix: number[][] = [];
32
- for (let i = 0; i <= b.length; i++) matrix[i] = [i];
33
- for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
34
-
35
- for (let i = 1; i <= b.length; i++) {
36
- for (let j = 1; j <= a.length; j++) {
37
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
38
- matrix[i][j] = matrix[i - 1][j - 1];
39
- } else {
40
- matrix[i][j] = Math.min(
41
- matrix[i - 1][j - 1] + 1,
42
- matrix[i][j - 1] + 1,
43
- matrix[i - 1][j] + 1
44
- );
45
- }
46
- }
47
- }
48
- return matrix[b.length][a.length];
49
- }
50
-
51
- @AutoScoped
52
- export class DiscardsExceptions extends ScopedVisitor {
53
- visitCatch(node: Catch): void {
54
- if (
55
- !node.body ||
56
- node.body instanceof NilPrimitive ||
57
- isSequenceEmpty(node.body)
58
- ) {
59
- throw new StopTraversalException();
60
- }
61
- }
62
- }
63
-
64
- @AutoScoped
65
- export class DoesConsolePrint extends ScopedVisitor {
66
- visitPrint(node: Print): void {
67
- throw new StopTraversalException();
68
- }
69
-
70
- visitCall(node: Call): void {
71
- const name = node.callee.value;
72
- if (this.isPrintFunc(name)) throw new StopTraversalException();
73
- }
74
-
75
- visitApplication(node: Application): void {
76
- const func = node.functionExpr;
77
- if (!(func instanceof SymbolPrimitive)) return func.accept(this);
78
- const name = func.value;
79
- if (this.isPrintFunc(name)) throw new StopTraversalException();
80
- }
81
- private isPrintFunc(name: string): boolean {
82
- const printFuncs = [
83
- "print",
84
- "println",
85
- "puts",
86
- "log",
87
- "console.log",
88
- "System.out.println",
89
- "fmt.Println",
90
- ];
91
- if (printFuncs.includes(name)) return true;
92
- return false;
93
- }
94
- }
95
-
96
- @AutoScoped
97
- export class HasDeclarationTypos extends ScopedVisitor {
98
- /*
99
- * Checks if two variables declared in the same scope are suspiciously similar
100
- * (Levenshtein distance <= 2), indicating a possible typo (e.g., 'count' vs 'conut').
101
- */
102
- visitSequence(node: Sequence): void {
103
- const declaredNames: string[] = [];
104
-
105
- for (const stmt of node.statements) {
106
- if (stmt instanceof Variable) declaredNames.push(stmt.identifier.value);
107
- }
108
-
109
- for (let i = 0; i < declaredNames.length; i++) {
110
- for (let j = i + 1; j < declaredNames.length; j++) {
111
- const a = declaredNames[i];
112
- const b = declaredNames[j];
113
- if (a.length > 3 && b.length > 3) {
114
- if (getLevenshteinDistance(a, b) <= 2) {
115
- throw new StopTraversalException();
116
- }
117
- }
118
- }
119
- }
120
- }
121
- }
122
-
123
- @AutoScoped
124
- export class HasEmptyIfBranches extends ScopedVisitor {
125
- visitIf(node: If): void {
126
- const isThenEmpty = !node.then || isSequenceEmpty(node.then);
127
- const isElseEmpty = node.elseExpr && isSequenceEmpty(node.elseExpr);
128
- if (isThenEmpty || isElseEmpty) throw new StopTraversalException();
129
- }
130
- }
131
-
132
- @AutoScoped
133
- export class HasLongParameterList extends ScopedVisitor {
134
- private readonly maxParams: number;
135
-
136
- constructor(maxParams: number = 5, scope?: string) {
137
- super(scope);
138
- this.maxParams = Number(maxParams);
139
- }
140
-
141
- visitEquation(node: Equation): void {
142
- const params = node.patterns;
143
- if (params.length > this.maxParams) throw new StopTraversalException();
144
- }
145
- }
146
-
147
- @AutoScoped
148
- export class HasMisspelledIdentifiers extends ScopedVisitor {
149
- private readonly dictionary: Set<string>;
150
-
151
- constructor(dictionaryWords: string[] = [], scope?: string) {
152
- super(scope);
153
- // In a real app, load a standard dictionary + jargon here
154
- this.dictionary = new Set(dictionaryWords.map((w) => w.toLowerCase()));
155
- }
156
-
157
- visitVariable(node: Variable): void {
158
- this.checkSpelling(node.identifier.value);
159
- }
160
-
161
- visitFunction(node: Function): void {
162
- this.checkSpelling(node.identifier.value);
163
- }
164
-
165
- private checkSpelling(word: string): void {
166
- // Skip if no dictionary provided or word is very short
167
- if (this.dictionary.size === 0 || word.length < 3) return;
168
-
169
- // Naive camelCase splitter
170
- const parts = word.split(/(?=[A-Z])|[-_]/);
171
-
172
- for (const part of parts) {
173
- if (!this.dictionary.has(part.toLowerCase()))
174
- throw new StopTraversalException();
175
- }
176
- }
177
- }
178
-
179
- @AutoScoped
180
- export class HasRedundantBooleanComparison extends ScopedVisitor {
181
- visitLogicalBinaryOperation(node: LogicalBinaryOperation): void {
182
- const isLeftBool = node.left instanceof BooleanPrimitive;
183
- const isRightBool = node.right instanceof BooleanPrimitive;
184
- if (isLeftBool || isRightBool) throw new StopTraversalException();
185
- }
186
- }
187
-
188
- @AutoScoped
189
- export class HasRedundantIf extends ScopedVisitor {
190
- visitIf(node: If): void {
191
- if (node.elseExpr) {
192
- const thenStmt = node.then;
193
- const elseStmt = node.elseExpr;
194
-
195
- if (thenStmt instanceof Sequence && elseStmt instanceof Sequence) {
196
- // Check if returning booleans
197
- if (
198
- thenStmt.statements.some(
199
- (stmt) => stmt instanceof BooleanPrimitive
200
- ) &&
201
- elseStmt.statements.some((stmt) => stmt instanceof BooleanPrimitive)
202
- ) {
203
- throw new StopTraversalException();
204
- }
205
- }
206
- }
207
- }
208
- }
209
-
210
- @AutoScoped
211
- export class HasRedundantLocalVariableReturn extends ScopedVisitor {
212
- /*
213
- * Detects:
214
- * var x = something;
215
- * return x;
216
- */
217
- visitSequence(node: Sequence): void {
218
- const stmts = node.statements;
219
- for (let i = 0; i < stmts.length - 1; i++) {
220
- const stmt = stmts[i];
221
- const nextStmt = stmts[i + 1];
222
-
223
- if (stmt instanceof Variable && nextStmt instanceof Return) {
224
- if (
225
- nextStmt.body instanceof SymbolPrimitive &&
226
- nextStmt.body.value === stmt.identifier.value
227
- ) {
228
- throw new StopTraversalException();
229
- }
230
- }
231
- }
232
- }
233
- }
234
-
235
- @AutoScoped
236
- export class HasTooShortIdentifiers extends ScopedVisitor {
237
- private readonly minLength: number;
238
-
239
- constructor(minLength: number = 3, scope?: string) {
240
- super(scope);
241
- this.minLength = Number(minLength);
242
- }
243
-
244
- visitVariable(node: Variable): void {
245
- // exclude common counters
246
- const allowed = ["i", "j", "k", "x", "y", "z", "id"];
247
- const name = node.identifier.value;
248
-
249
- if (name.length < this.minLength && !allowed.includes(name)) {
250
- throw new StopTraversalException();
251
- }
252
- }
253
- }
254
-
255
- @AutoScoped
256
- export class HasUsageTypos extends ScopedVisitor {
257
- visitSequence(node: Sequence): void {
258
- const declaredNames = new Set<string>();
259
- const usedNames = new Set<string>();
260
-
261
- for (const stmt of node.statements) {
262
- if (stmt instanceof Function || stmt instanceof Variable) {
263
- declaredNames.add(stmt.identifier.value);
264
- }
265
- }
266
-
267
- for (const stmt of node.statements) {
268
- if (stmt instanceof Call) usedNames.add(stmt.callee.value);
269
- }
270
-
271
- for (const usage of usedNames) {
272
- if (!declaredNames.has(usage)) {
273
- for (const decl of declaredNames) {
274
- if (usage.length > 3 && getLevenshteinDistance(usage, decl) <= 1) {
275
- throw new StopTraversalException();
276
- }
277
- }
278
- }
279
- }
280
- }
281
- }
282
-
283
- @AutoScoped
284
- export class UsesWrongCaseBindings extends ScopedVisitor {
285
- private readonly caseType: "camel" | "snake" | "pascal";
286
-
287
- constructor(caseType: string = "camel", scopeName?: string) {
288
- super(scopeName);
289
- this.caseType = caseType as any;
290
- }
291
-
292
- visitSymbolPrimitive(node: SymbolPrimitive): void {
293
- this.checkCase(node.value);
294
- }
295
-
296
- private checkCase(name: string): void {
297
- // Ignore operators (names not starting with a letter or underscore)
298
- if (!/^[a-zA-Z_]/.test(name)) return;
299
-
300
- let regex: RegExp;
301
-
302
- switch (this.caseType) {
303
- case "snake":
304
- regex = /^[a-z][a-z0-9_]*$/;
305
- break;
306
- case "pascal":
307
- regex = /^[A-Z][a-zA-Z0-9]*$/;
308
- break;
309
- case "camel":
310
- default:
311
- regex = /^[a-z][a-zA-Z0-9]*$/;
312
- break;
313
- }
314
-
315
- if (!regex.test(name)) {
316
- throw new StopTraversalException();
317
- }
318
- }
319
- }
320
-
321
- @AutoScoped
322
- export class IsLongCode extends ScopedVisitor {
323
- private readonly maxStatements: number;
324
-
325
- constructor(maxStatements: number = 50, scopeName?: string) {
326
- super(scopeName);
327
- this.maxStatements = Number(maxStatements);
328
- }
329
-
330
- visitSequence(node: Sequence): void {
331
- if (node.statements.length > this.maxStatements) {
332
- throw new StopTraversalException();
333
- }
334
- }
335
- }
336
-
337
- @AutoScoped
338
- export class ShouldInvertIfCondition extends ScopedVisitor {
339
- visitIf(node: If): void {
340
- const thenEmpty = !node.then || isSequenceEmpty(node.then);
341
- const elseNonEmpty = node.elseExpr && !isSequenceEmpty(node.elseExpr);
342
-
343
- if (thenEmpty && elseNonEmpty) {
344
- throw new StopTraversalException();
345
- }
346
- }
347
- }
348
-
349
- export const genericSmells: Record<string, VisitorConstructor> = {
350
- DiscardsExceptions: DiscardsExceptions,
351
- DoesConsolePrint: DoesConsolePrint,
352
- HasDeclarationTypos: HasDeclarationTypos,
353
- HasEmptyIfBranches: HasEmptyIfBranches,
354
- HasLongParameterList: HasLongParameterList,
355
- HasMisspelledIdentifiers: HasMisspelledIdentifiers,
356
- HasRedundantBooleanComparison: HasRedundantBooleanComparison,
357
- HasRedundantIf: HasRedundantIf,
358
- HasRedundantLocalVariableReturn: HasRedundantLocalVariableReturn,
359
- HasTooShortIdentifiers: HasTooShortIdentifiers,
360
- HasUsageTypos: HasUsageTypos,
361
- HasWrongCaseIdentifiers: UsesWrongCaseBindings,
362
- UsesWrongCaseBindings: UsesWrongCaseBindings,
363
- IsLongCode: IsLongCode,
364
- ShouldInvertIfCondition: ShouldInvertIfCondition,
365
- };
1
+ import {
2
+ Application,
3
+ ASTNode,
4
+ BooleanPrimitive,
5
+ Call,
6
+ Catch,
7
+ Equation,
8
+ Expression,
9
+ Function,
10
+ If,
11
+ LogicalBinaryOperation,
12
+ NilPrimitive,
13
+ Print,
14
+ Return,
15
+ Sequence,
16
+ StopTraversalException,
17
+ SymbolPrimitive,
18
+ Variable,
19
+ VariablePattern,
20
+ } from "yukigo-ast";
21
+ import { AutoScoped, ScopedVisitor, VisitorConstructor } from "../../utils.js";
22
+
23
+ function isSequenceEmpty(node: Expression): boolean {
24
+ return node.is(Sequence) && node.statements.length === 0;
25
+ }
26
+
27
+ /**
28
+ * Basic Levenshtein distance for typo detection
29
+ */
30
+ function getLevenshteinDistance(a: string, b: string): number {
31
+ const matrix: number[][] = [];
32
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
33
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
34
+
35
+ for (let i = 1; i <= b.length; i++) {
36
+ for (let j = 1; j <= a.length; j++) {
37
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
38
+ matrix[i][j] = matrix[i - 1][j - 1];
39
+ } else {
40
+ matrix[i][j] = Math.min(
41
+ matrix[i - 1][j - 1] + 1,
42
+ matrix[i][j - 1] + 1,
43
+ matrix[i - 1][j] + 1,
44
+ );
45
+ }
46
+ }
47
+ }
48
+ return matrix[b.length][a.length];
49
+ }
50
+
51
+ @AutoScoped
52
+ export class DiscardsExceptions extends ScopedVisitor {
53
+ visitCatch(node: Catch): void {
54
+ const doesNotCatch =
55
+ !node.body || node.body.is(NilPrimitive) || isSequenceEmpty(node.body);
56
+ if (doesNotCatch) {
57
+ throw new StopTraversalException();
58
+ }
59
+ }
60
+ }
61
+
62
+ @AutoScoped
63
+ export class DoesConsolePrint extends ScopedVisitor {
64
+ visitPrint(node: Print): void {
65
+ throw new StopTraversalException();
66
+ }
67
+
68
+ visitCall(node: Call): void {
69
+ const name = node.callee.value;
70
+ if (this.isPrintFunc(name)) throw new StopTraversalException();
71
+ }
72
+
73
+ visitApplication(node: Application): void {
74
+ const func = node.functionExpr;
75
+ if (!func.is(SymbolPrimitive)) return func.accept(this);
76
+ const name = func.value;
77
+ if (this.isPrintFunc(name)) throw new StopTraversalException();
78
+ }
79
+ private isPrintFunc(name: string): boolean {
80
+ const printFuncs = [
81
+ "print",
82
+ "println",
83
+ "puts",
84
+ "log",
85
+ "console.log",
86
+ "System.out.println",
87
+ "fmt.Println",
88
+ ];
89
+ if (printFuncs.includes(name)) return true;
90
+ return false;
91
+ }
92
+ }
93
+
94
+ @AutoScoped
95
+ export class HasDeclarationTypos extends ScopedVisitor {
96
+ /*
97
+ * Checks if two variables declared in the same scope are suspiciously similar
98
+ * (Levenshtein distance <= 2), indicating a possible typo (e.g., 'count' vs 'conut').
99
+ */
100
+ visitSequence(node: Sequence): void {
101
+ const names = node.statements
102
+ .filter((stmt) => stmt.is(Variable))
103
+ .map((stmt) => stmt.identifier.value);
104
+
105
+ names.forEach((nameA, i) => {
106
+ const hasTypo = names.slice(i + 1).some((nameB) => {
107
+ if (nameA.length <= 3 || nameB.length <= 3) return false;
108
+ return getLevenshteinDistance(nameA, nameB) <= 2;
109
+ });
110
+
111
+ if (hasTypo) throw new StopTraversalException();
112
+ });
113
+ }
114
+ }
115
+
116
+ @AutoScoped
117
+ export class HasEmptyIfBranches extends ScopedVisitor {
118
+ visitIf(node: If): void {
119
+ const isThenEmpty = !node.then || isSequenceEmpty(node.then);
120
+ const isElseEmpty = isSequenceEmpty(node.elseExpr);
121
+ if (isThenEmpty || isElseEmpty) throw new StopTraversalException();
122
+ }
123
+ }
124
+
125
+ @AutoScoped
126
+ export class HasLongParameterList extends ScopedVisitor {
127
+ private readonly maxParams: number;
128
+
129
+ constructor(maxParams: number = 5, scope?: string) {
130
+ super(scope);
131
+ this.maxParams = Number(maxParams);
132
+ }
133
+
134
+ visitEquation(node: Equation): void {
135
+ const params = node.patterns;
136
+ if (params.length > this.maxParams) throw new StopTraversalException();
137
+ }
138
+ }
139
+
140
+ @AutoScoped
141
+ export class HasMisspelledIdentifiers extends ScopedVisitor {
142
+ private readonly dictionary: Set<string>;
143
+
144
+ constructor(dictionaryWords: string[] = [], scope?: string) {
145
+ super(scope);
146
+ // In a real app, load a standard dictionary + jargon here
147
+ this.dictionary = new Set(dictionaryWords.map((w) => w.toLowerCase()));
148
+ }
149
+
150
+ visitVariable(node: Variable): void {
151
+ this.checkSpelling(node.identifier.value);
152
+ }
153
+
154
+ visitFunction(node: Function): void {
155
+ this.checkSpelling(node.identifier.value);
156
+ }
157
+
158
+ private checkSpelling(word: string): void {
159
+ // Skip if no dictionary provided or word is very short
160
+ if (this.dictionary.size === 0 || word.length < 3) return;
161
+
162
+ // Naive camelCase splitter
163
+ const parts = word.split(/(?=[A-Z])|[-_]/);
164
+
165
+ for (const part of parts) {
166
+ if (!this.dictionary.has(part.toLowerCase()))
167
+ throw new StopTraversalException();
168
+ }
169
+ }
170
+ }
171
+
172
+ @AutoScoped
173
+ export class HasRedundantBooleanComparison extends ScopedVisitor {
174
+ visitLogicalBinaryOperation(node: LogicalBinaryOperation): void {
175
+ const isLeftBool = node.left.is(BooleanPrimitive);
176
+ const isRightBool = node.right.is(BooleanPrimitive);
177
+ if (isLeftBool || isRightBool) throw new StopTraversalException();
178
+ }
179
+ }
180
+
181
+ @AutoScoped
182
+ export class HasRedundantIf extends ScopedVisitor {
183
+ visitIf(node: If): void {
184
+ if (!node.elseExpr) return;
185
+ const { then, elseExpr } = node;
186
+
187
+ if (this.isBooleanBlock(then) && this.isBooleanBlock(elseExpr))
188
+ throw new StopTraversalException();
189
+ }
190
+ private isBooleanBlock(node: ASTNode): boolean {
191
+ return (
192
+ node.is(Sequence) && node.statements.some((s) => s.is(BooleanPrimitive))
193
+ );
194
+ }
195
+ }
196
+
197
+ @AutoScoped
198
+ export class HasRedundantLocalVariableReturn extends ScopedVisitor {
199
+ /*
200
+ * Detects:
201
+ * var x = something;
202
+ * return x;
203
+ */
204
+ visitSequence(node: Sequence): void {
205
+ const stmts = node.statements;
206
+
207
+ stmts.forEach((stmt, i) => {
208
+ const next = stmts[i + 1];
209
+ if (!next) return;
210
+
211
+ if (this.isPointlessAssignment(stmt, next)) {
212
+ throw new StopTraversalException();
213
+ }
214
+ });
215
+ }
216
+ private isPointlessAssignment(current: ASTNode, next: ASTNode): boolean {
217
+ return (
218
+ current.is(Variable) &&
219
+ next.is(Return) &&
220
+ next.body !== undefined &&
221
+ next.body.is(SymbolPrimitive) &&
222
+ next.body.value === current.identifier.value
223
+ );
224
+ }
225
+ }
226
+
227
+ @AutoScoped
228
+ export class HasTooShortIdentifiers extends ScopedVisitor {
229
+ private readonly minLength: number;
230
+
231
+ constructor(minLength: number = 3, scope?: string) {
232
+ super(scope);
233
+ this.minLength = Number(minLength);
234
+ }
235
+
236
+ visitVariable(node: Variable): void {
237
+ // exclude common counters
238
+ const allowed = ["i", "j", "k", "x", "y", "z", "id"];
239
+ const name = node.identifier.value;
240
+
241
+ if (name.length < this.minLength && !allowed.includes(name)) {
242
+ throw new StopTraversalException();
243
+ }
244
+ }
245
+ }
246
+
247
+ @AutoScoped
248
+ export class HasUsageTypos extends ScopedVisitor {
249
+ visitSequence(node: Sequence): void {
250
+ const { statements } = node;
251
+ const declared = new Set(
252
+ statements
253
+ .filter((stmt) => stmt.is(Function) || stmt.is(Variable))
254
+ .map((stmt) => stmt.identifier.value),
255
+ );
256
+
257
+ const called = statements
258
+ .filter((stmt) => stmt.is(Call))
259
+ .map((stmt) => stmt.callee.value);
260
+
261
+ const hasTypo = called.some((usage) => {
262
+ if (declared.has(usage) || usage.length <= 3) return false;
263
+
264
+ return Array.from(declared).some(
265
+ (decl) => getLevenshteinDistance(usage, decl) <= 1,
266
+ );
267
+ });
268
+
269
+ if (hasTypo) throw new StopTraversalException();
270
+ }
271
+ }
272
+
273
+ @AutoScoped
274
+ export class UsesWrongCaseBindings extends ScopedVisitor {
275
+ private readonly caseType: "camel" | "snake" | "pascal";
276
+
277
+ constructor(caseType: string = "camel", scopeName?: string) {
278
+ super(scopeName);
279
+ this.caseType = caseType as any;
280
+ }
281
+
282
+ visitSymbolPrimitive(node: SymbolPrimitive): void {
283
+ this.checkCase(node.value);
284
+ }
285
+
286
+ private checkCase(name: string): void {
287
+ // Ignore operators (names not starting with a letter or underscore)
288
+ if (!/^[a-zA-Z_]/.test(name)) return;
289
+
290
+ let regex: RegExp;
291
+
292
+ switch (this.caseType) {
293
+ case "snake":
294
+ regex = /^[a-z][a-z0-9_]*$/;
295
+ break;
296
+ case "pascal":
297
+ regex = /^[A-Z][a-zA-Z0-9]*$/;
298
+ break;
299
+ case "camel":
300
+ default:
301
+ regex = /^[a-z][a-zA-Z0-9]*$/;
302
+ break;
303
+ }
304
+
305
+ if (!regex.test(name)) {
306
+ throw new StopTraversalException();
307
+ }
308
+ }
309
+ }
310
+
311
+ @AutoScoped
312
+ export class IsLongCode extends ScopedVisitor {
313
+ private readonly maxStatements: number;
314
+
315
+ constructor(maxStatements: number = 50, scopeName?: string) {
316
+ super(scopeName);
317
+ this.maxStatements = Number(maxStatements);
318
+ }
319
+
320
+ visitSequence(node: Sequence): void {
321
+ if (node.statements.length > this.maxStatements) {
322
+ throw new StopTraversalException();
323
+ }
324
+ }
325
+ }
326
+
327
+ @AutoScoped
328
+ export class ShouldInvertIfCondition extends ScopedVisitor {
329
+ visitIf(node: If): void {
330
+ const thenEmpty = !node.then || isSequenceEmpty(node.then);
331
+ const elseNonEmpty = node.elseExpr && !isSequenceEmpty(node.elseExpr);
332
+
333
+ if (thenEmpty && elseNonEmpty) {
334
+ throw new StopTraversalException();
335
+ }
336
+ }
337
+ }
338
+
339
+ export const genericSmells: Record<string, VisitorConstructor> = {
340
+ DiscardsExceptions: DiscardsExceptions,
341
+ DoesConsolePrint: DoesConsolePrint,
342
+ HasDeclarationTypos: HasDeclarationTypos,
343
+ HasEmptyIfBranches: HasEmptyIfBranches,
344
+ HasLongParameterList: HasLongParameterList,
345
+ HasMisspelledIdentifiers: HasMisspelledIdentifiers,
346
+ HasRedundantBooleanComparison: HasRedundantBooleanComparison,
347
+ HasRedundantIf: HasRedundantIf,
348
+ HasRedundantLocalVariableReturn: HasRedundantLocalVariableReturn,
349
+ HasTooShortIdentifiers: HasTooShortIdentifiers,
350
+ HasUsageTypos: HasUsageTypos,
351
+ HasWrongCaseIdentifiers: UsesWrongCaseBindings,
352
+ UsesWrongCaseBindings: UsesWrongCaseBindings,
353
+ IsLongCode: IsLongCode,
354
+ ShouldInvertIfCondition: ShouldInvertIfCondition,
355
+ };