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.
- package/.mocharc.json +3 -3
- package/CHANGELOG.md +26 -0
- package/README.md +193 -199
- package/dist/analyzer/GraphBuilder.d.ts +29 -0
- package/dist/analyzer/GraphBuilder.js +99 -0
- package/dist/analyzer/index.d.ts +11 -23
- package/dist/analyzer/index.js +100 -58
- package/dist/analyzer/inspections/functional/functional.d.ts +44 -0
- package/dist/analyzer/inspections/functional/functional.js +149 -0
- package/dist/analyzer/inspections/functional/smells.d.ts +16 -0
- package/dist/analyzer/inspections/functional/smells.js +98 -0
- package/dist/analyzer/inspections/{generic.d.ts → generic/generic.d.ts} +70 -43
- package/dist/analyzer/inspections/generic/generic.js +604 -0
- package/dist/analyzer/inspections/generic/smells.d.ts +61 -0
- package/dist/analyzer/inspections/generic/smells.js +349 -0
- package/dist/analyzer/inspections/imperative/imperative.d.ts +35 -0
- package/dist/analyzer/inspections/imperative/imperative.js +109 -0
- package/dist/analyzer/inspections/imperative/smells.d.ts +16 -0
- package/dist/analyzer/inspections/imperative/smells.js +58 -0
- package/dist/analyzer/inspections/logic/logic.d.ts +32 -0
- package/dist/analyzer/inspections/logic/logic.js +96 -0
- package/dist/analyzer/inspections/logic/smells.d.ts +15 -0
- package/dist/analyzer/inspections/logic/smells.js +60 -0
- package/dist/analyzer/inspections/object/object.d.ts +88 -0
- package/dist/analyzer/inspections/object/object.js +319 -0
- package/dist/analyzer/inspections/object/smells.d.ts +30 -0
- package/dist/analyzer/inspections/object/smells.js +135 -0
- package/dist/analyzer/utils.d.ts +26 -4
- package/dist/analyzer/utils.js +71 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interpreter/components/EnvBuilder.d.ts +9 -5
- package/dist/interpreter/components/EnvBuilder.js +100 -30
- package/dist/interpreter/components/Operations.d.ts +4 -4
- package/dist/interpreter/components/Operations.js +17 -2
- package/dist/interpreter/components/PatternMatcher.d.ts +47 -17
- package/dist/interpreter/components/PatternMatcher.js +264 -119
- package/dist/interpreter/components/RuntimeContext.d.ts +35 -0
- package/dist/interpreter/components/RuntimeContext.js +93 -0
- package/dist/interpreter/components/TestRunner.d.ts +18 -0
- package/dist/interpreter/components/TestRunner.js +103 -0
- package/dist/interpreter/components/Visitor.d.ts +63 -57
- package/dist/interpreter/components/Visitor.js +508 -173
- package/dist/interpreter/components/logic/LogicEngine.d.ts +29 -0
- package/dist/interpreter/components/logic/LogicEngine.js +259 -0
- package/dist/interpreter/components/logic/LogicResolver.d.ts +53 -0
- package/dist/interpreter/components/logic/LogicResolver.js +471 -0
- package/dist/interpreter/components/logic/LogicTranslator.d.ts +14 -0
- package/dist/interpreter/components/logic/LogicTranslator.js +99 -0
- package/dist/interpreter/components/runtimes/FunctionRuntime.d.ts +12 -0
- package/dist/interpreter/components/runtimes/FunctionRuntime.js +147 -0
- package/dist/interpreter/components/runtimes/LazyRuntime.d.ts +19 -0
- package/dist/interpreter/components/runtimes/LazyRuntime.js +269 -0
- package/dist/interpreter/components/runtimes/ObjectRuntime.d.ts +35 -0
- package/dist/interpreter/components/runtimes/ObjectRuntime.js +126 -0
- package/dist/interpreter/entities.d.ts +105 -0
- package/dist/interpreter/entities.js +96 -0
- package/dist/interpreter/errors.d.ts +1 -1
- package/dist/interpreter/index.d.ts +4 -12
- package/dist/interpreter/index.js +10 -13
- package/dist/interpreter/trampoline.d.ts +17 -0
- package/dist/interpreter/trampoline.js +38 -0
- package/dist/interpreter/utils.d.ts +4 -7
- package/dist/interpreter/utils.js +25 -17
- package/dist/tester/index.d.ts +25 -0
- package/dist/tester/index.js +108 -0
- package/dist/utils/helpers.d.ts +0 -4
- package/dist/utils/helpers.js +20 -24
- package/package.json +2 -2
- package/src/analyzer/GraphBuilder.ts +142 -0
- package/src/analyzer/index.ts +185 -132
- package/src/analyzer/inspections/functional/functional.ts +121 -0
- package/src/analyzer/inspections/functional/smells.ts +102 -0
- package/src/analyzer/inspections/{generic.ts → generic/generic.ts} +581 -499
- package/src/analyzer/inspections/generic/smells.ts +365 -0
- package/src/analyzer/inspections/imperative/imperative.ts +101 -0
- package/src/analyzer/inspections/imperative/smells.ts +54 -0
- package/src/analyzer/inspections/logic/logic.ts +90 -0
- package/src/analyzer/inspections/logic/smells.ts +54 -0
- package/src/analyzer/inspections/{object.ts → object/object.ts} +264 -282
- package/src/analyzer/inspections/object/smells.ts +144 -0
- package/src/analyzer/utils.ts +109 -26
- package/src/index.ts +3 -2
- package/src/interpreter/components/EnvBuilder.ts +202 -97
- package/src/interpreter/components/Operations.ts +99 -81
- package/src/interpreter/components/PatternMatcher.ts +475 -254
- package/src/interpreter/components/RuntimeContext.ts +119 -0
- package/src/interpreter/components/TestRunner.ts +151 -0
- package/src/interpreter/components/Visitor.ts +1065 -493
- package/src/interpreter/components/logic/LogicEngine.ts +519 -0
- package/src/interpreter/components/logic/LogicResolver.ts +858 -0
- package/src/interpreter/components/logic/LogicTranslator.ts +149 -0
- package/src/interpreter/components/runtimes/FunctionRuntime.ts +227 -0
- package/src/interpreter/components/runtimes/LazyRuntime.ts +334 -0
- package/src/interpreter/components/runtimes/ObjectRuntime.ts +224 -0
- package/src/interpreter/errors.ts +47 -47
- package/src/interpreter/index.ts +52 -59
- package/src/interpreter/trampoline.ts +71 -0
- package/src/interpreter/utils.ts +84 -79
- package/src/tester/index.ts +128 -0
- package/src/utils/helpers.ts +67 -73
- package/tests/analyzer/functional.spec.ts +207 -221
- package/tests/analyzer/generic.spec.ts +178 -100
- package/tests/analyzer/helpers.spec.ts +83 -83
- package/tests/analyzer/logic.spec.ts +237 -292
- package/tests/analyzer/oop.spec.ts +323 -338
- package/tests/analyzer/transitive.spec.ts +166 -0
- package/tests/interpreter/EnvBuilder.spec.ts +183 -178
- package/tests/interpreter/FunctionRuntime.spec.ts +223 -234
- package/tests/interpreter/LazyRuntime.spec.ts +225 -190
- package/tests/interpreter/LogicEngine.spec.ts +327 -194
- package/tests/interpreter/LogicSubstitution.spec.ts +80 -0
- package/tests/interpreter/ObjectRuntime.spec.ts +606 -0
- package/tests/interpreter/Operations.spec.ts +220 -220
- package/tests/interpreter/PatternSystem.spec.ts +213 -189
- package/tests/interpreter/Tests.spec.ts +122 -0
- package/tests/interpreter/interpreter.spec.ts +991 -937
- package/tests/tester/Tester.spec.ts +153 -0
- package/tsconfig.build.json +15 -7
- package/tsconfig.json +25 -17
- package/dist/analyzer/inspections/functional.d.ts +0 -46
- package/dist/analyzer/inspections/functional.js +0 -123
- package/dist/analyzer/inspections/generic.js +0 -427
- package/dist/analyzer/inspections/imperative.d.ts +0 -37
- package/dist/analyzer/inspections/imperative.js +0 -105
- package/dist/analyzer/inspections/logic.d.ts +0 -49
- package/dist/analyzer/inspections/logic.js +0 -140
- package/dist/analyzer/inspections/object.d.ts +0 -83
- package/dist/analyzer/inspections/object.js +0 -235
- package/dist/interpreter/components/FunctionRuntime.d.ts +0 -8
- package/dist/interpreter/components/FunctionRuntime.js +0 -52
- package/dist/interpreter/components/LazyRuntime.d.ts +0 -7
- package/dist/interpreter/components/LazyRuntime.js +0 -75
- package/dist/interpreter/components/LogicEngine.d.ts +0 -21
- package/dist/interpreter/components/LogicEngine.js +0 -152
- package/dist/interpreter/components/LogicResolver.d.ts +0 -11
- package/dist/interpreter/components/LogicResolver.js +0 -87
- package/src/analyzer/inspections/functional.ts +0 -159
- package/src/analyzer/inspections/imperative.ts +0 -129
- package/src/analyzer/inspections/logic.ts +0 -166
- package/src/interpreter/components/FunctionRuntime.ts +0 -79
- package/src/interpreter/components/LazyRuntime.ts +0 -97
- package/src/interpreter/components/LogicEngine.ts +0 -227
- package/src/interpreter/components/LogicResolver.ts +0 -130
|
@@ -0,0 +1,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 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
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Enumeration,
|
|
3
|
+
ForLoop,
|
|
4
|
+
Function,
|
|
5
|
+
Procedure,
|
|
6
|
+
Repeat,
|
|
7
|
+
StopTraversalException,
|
|
8
|
+
Switch,
|
|
9
|
+
TraverseVisitor,
|
|
10
|
+
While,
|
|
11
|
+
} from "yukigo-ast";
|
|
12
|
+
import { AutoScoped, ScopedVisitor, VisitorConstructor } from "../../utils.js";
|
|
13
|
+
|
|
14
|
+
export class DeclaresEnumeration extends TraverseVisitor {
|
|
15
|
+
private readonly enumName: string;
|
|
16
|
+
constructor(enumName: string) {
|
|
17
|
+
super();
|
|
18
|
+
this.enumName = enumName;
|
|
19
|
+
}
|
|
20
|
+
visitEnumeration(node: Enumeration): void {
|
|
21
|
+
if (node.identifier.value === this.enumName)
|
|
22
|
+
throw new StopTraversalException();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
@AutoScoped
|
|
26
|
+
export class DeclaresProcedure extends ScopedVisitor {
|
|
27
|
+
private readonly procedureName: string;
|
|
28
|
+
constructor(procedureName: string, scope?: string) {
|
|
29
|
+
super(scope);
|
|
30
|
+
this.procedureName = procedureName;
|
|
31
|
+
}
|
|
32
|
+
visitProcedure(node: Procedure): void {
|
|
33
|
+
if (node.identifier.value === this.procedureName)
|
|
34
|
+
throw new StopTraversalException();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@AutoScoped
|
|
39
|
+
export class UsesForLoop extends ScopedVisitor {
|
|
40
|
+
constructor(scope?: string) {
|
|
41
|
+
super(scope);
|
|
42
|
+
}
|
|
43
|
+
visitForLoop(node: ForLoop): void {
|
|
44
|
+
throw new StopTraversalException();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
@AutoScoped
|
|
48
|
+
export class UsesWhile extends ScopedVisitor {
|
|
49
|
+
constructor(scope?: string) {
|
|
50
|
+
super(scope);
|
|
51
|
+
}
|
|
52
|
+
visitWhile(node: While): void {
|
|
53
|
+
throw new StopTraversalException();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
@AutoScoped
|
|
57
|
+
export class UsesRepeat extends ScopedVisitor {
|
|
58
|
+
constructor(scope?: string) {
|
|
59
|
+
super(scope);
|
|
60
|
+
}
|
|
61
|
+
visitRepeat(node: Repeat): void {
|
|
62
|
+
throw new StopTraversalException();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
@AutoScoped
|
|
66
|
+
export class UsesLoop extends ScopedVisitor {
|
|
67
|
+
constructor(scope?: string) {
|
|
68
|
+
super(scope);
|
|
69
|
+
}
|
|
70
|
+
visitForLoop(node: ForLoop): void {
|
|
71
|
+
throw new StopTraversalException();
|
|
72
|
+
}
|
|
73
|
+
visitWhile(node: While): void {
|
|
74
|
+
throw new StopTraversalException();
|
|
75
|
+
}
|
|
76
|
+
visitRepeat(node: Repeat): void {
|
|
77
|
+
throw new StopTraversalException();
|
|
78
|
+
}
|
|
79
|
+
// visitForEach(node: ForEach): void {
|
|
80
|
+
// throw StopTraversalException;
|
|
81
|
+
// }
|
|
82
|
+
}
|
|
83
|
+
@AutoScoped
|
|
84
|
+
export class UsesSwitch extends ScopedVisitor {
|
|
85
|
+
constructor(scope?: string) {
|
|
86
|
+
super(scope);
|
|
87
|
+
}
|
|
88
|
+
visitSwitch(node: Switch): void {
|
|
89
|
+
throw new StopTraversalException();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const imperativeInspections: Record<string, VisitorConstructor> = {
|
|
94
|
+
DeclaresEnumeration: DeclaresEnumeration,
|
|
95
|
+
DeclaresProcedure: DeclaresProcedure,
|
|
96
|
+
UsesForLoop: UsesForLoop,
|
|
97
|
+
UsesRepeat: UsesRepeat,
|
|
98
|
+
UsesWhile: UsesWhile,
|
|
99
|
+
UsesLoop: UsesLoop,
|
|
100
|
+
UsesSwitch: UsesSwitch,
|
|
101
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Assignment,
|
|
3
|
+
If,
|
|
4
|
+
NumberPrimitive,
|
|
5
|
+
Repeat,
|
|
6
|
+
Return,
|
|
7
|
+
Sequence,
|
|
8
|
+
StopTraversalException,
|
|
9
|
+
While,
|
|
10
|
+
} from "yukigo-ast";
|
|
11
|
+
import { AutoScoped, ScopedVisitor, VisitorConstructor } from "../../utils.js";
|
|
12
|
+
|
|
13
|
+
@AutoScoped
|
|
14
|
+
export class HasAssignmentCondition extends ScopedVisitor {
|
|
15
|
+
visitIf(node: If): void {
|
|
16
|
+
if (node.condition instanceof Assignment)
|
|
17
|
+
throw new StopTraversalException();
|
|
18
|
+
}
|
|
19
|
+
visitWhile(node: While): void {
|
|
20
|
+
if (node.condition instanceof Assignment)
|
|
21
|
+
throw new StopTraversalException();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@AutoScoped
|
|
26
|
+
export class HasAssignmentReturn extends ScopedVisitor {
|
|
27
|
+
visitReturn(node: Return): void {
|
|
28
|
+
if (node.body && node.body instanceof Assignment)
|
|
29
|
+
throw new StopTraversalException();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@AutoScoped
|
|
34
|
+
export class HasEmptyRepeat extends ScopedVisitor {
|
|
35
|
+
visitRepeat(node: Repeat): void {
|
|
36
|
+
if (node.body instanceof Sequence && node.body.statements.length === 0)
|
|
37
|
+
throw new StopTraversalException();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@AutoScoped
|
|
42
|
+
export class HasRedundantRepeat extends ScopedVisitor {
|
|
43
|
+
visitRepeat(node: Repeat): void {
|
|
44
|
+
if (node.count instanceof NumberPrimitive && node.count.value === 1)
|
|
45
|
+
throw new StopTraversalException();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const imperativeSmells: Record<string, VisitorConstructor> = {
|
|
50
|
+
HasAssignmentCondition: HasAssignmentCondition,
|
|
51
|
+
HasAssignmentReturn: HasAssignmentReturn,
|
|
52
|
+
HasEmptyRepeat: HasEmptyRepeat,
|
|
53
|
+
HasRedundantRepeat: HasRedundantRepeat,
|
|
54
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Assignment,
|
|
3
|
+
AssignOperation,
|
|
4
|
+
Exist,
|
|
5
|
+
Fact,
|
|
6
|
+
Findall,
|
|
7
|
+
Forall,
|
|
8
|
+
Not,
|
|
9
|
+
Rule,
|
|
10
|
+
StopTraversalException,
|
|
11
|
+
SymbolPrimitive,
|
|
12
|
+
TraverseVisitor,
|
|
13
|
+
UnifyOperation,
|
|
14
|
+
} from "yukigo-ast";
|
|
15
|
+
import { AutoScoped, ScopedVisitor, VisitorConstructor } from "../../utils.js";
|
|
16
|
+
import { isYukigoPrimitive } from "yukigo-ast";
|
|
17
|
+
|
|
18
|
+
export class DeclaresFact extends TraverseVisitor {
|
|
19
|
+
constructor(private readonly target?: string) {
|
|
20
|
+
super();
|
|
21
|
+
}
|
|
22
|
+
visitFact(node: Fact): void {
|
|
23
|
+
const bindingName = node.identifier.value;
|
|
24
|
+
if (!this.target || bindingName === this.target)
|
|
25
|
+
throw new StopTraversalException();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class DeclaresRule extends TraverseVisitor {
|
|
29
|
+
constructor(private readonly target?: string) {
|
|
30
|
+
super();
|
|
31
|
+
}
|
|
32
|
+
visitRule(node: Rule): void {
|
|
33
|
+
const bindingName = node.identifier.value;
|
|
34
|
+
if (!this.target || bindingName === this.target)
|
|
35
|
+
throw new StopTraversalException();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class DeclaresPredicate extends TraverseVisitor {
|
|
39
|
+
constructor(private readonly target?: string) {
|
|
40
|
+
super();
|
|
41
|
+
}
|
|
42
|
+
visitFact(node: Fact): void {
|
|
43
|
+
new DeclaresFact(this.target).visitFact(node);
|
|
44
|
+
}
|
|
45
|
+
visitRule(node: Rule): void {
|
|
46
|
+
new DeclaresRule(this.target).visitRule(node);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
@AutoScoped
|
|
50
|
+
export class UsesFindall extends ScopedVisitor {
|
|
51
|
+
constructor(scope?: string) {
|
|
52
|
+
super(scope);
|
|
53
|
+
}
|
|
54
|
+
visitFindall(node: Findall): void {
|
|
55
|
+
throw new StopTraversalException();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
@AutoScoped
|
|
59
|
+
export class UsesForall extends ScopedVisitor {
|
|
60
|
+
constructor(scope?: string) {
|
|
61
|
+
super(scope);
|
|
62
|
+
}
|
|
63
|
+
visitForall(node: Forall): void {
|
|
64
|
+
throw new StopTraversalException();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
@AutoScoped
|
|
68
|
+
export class UsesNot extends ScopedVisitor {
|
|
69
|
+
constructor(scope?: string) {
|
|
70
|
+
super(scope);
|
|
71
|
+
}
|
|
72
|
+
visitNot(node: Not): void {
|
|
73
|
+
throw new StopTraversalException();
|
|
74
|
+
}
|
|
75
|
+
visitExist(node: Exist): void {
|
|
76
|
+
if (node.identifier.value === "not") throw new StopTraversalException();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const logicInspections: Record<string, VisitorConstructor> = {
|
|
81
|
+
DeclaresFact: DeclaresFact,
|
|
82
|
+
DeclaresRule: DeclaresRule,
|
|
83
|
+
DeclaresPredicate: DeclaresPredicate,
|
|
84
|
+
UsesFindall: UsesFindall,
|
|
85
|
+
HasFindall: UsesFindall,
|
|
86
|
+
UsesForall: UsesForall,
|
|
87
|
+
HasForall: UsesForall,
|
|
88
|
+
UsesNot: UsesNot,
|
|
89
|
+
HasNot: UsesNot,
|
|
90
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AssignOperation,
|
|
3
|
+
Exist,
|
|
4
|
+
isYukigoPrimitive,
|
|
5
|
+
Rule,
|
|
6
|
+
StopTraversalException,
|
|
7
|
+
SymbolPrimitive,
|
|
8
|
+
UnifyOperation,
|
|
9
|
+
} from "yukigo-ast";
|
|
10
|
+
import { AutoScoped, ScopedVisitor, VisitorConstructor } from "../../utils.js";
|
|
11
|
+
|
|
12
|
+
@AutoScoped
|
|
13
|
+
export class HasRedundantReduction extends ScopedVisitor {
|
|
14
|
+
visitAssignOperation(node: AssignOperation): void {
|
|
15
|
+
const left = node.left;
|
|
16
|
+
const right = node.right;
|
|
17
|
+
|
|
18
|
+
if (!(left instanceof SymbolPrimitive)) return;
|
|
19
|
+
|
|
20
|
+
const redundantReductionParameters = isYukigoPrimitive(right);
|
|
21
|
+
const redundantReductionFunctors = right instanceof Exist;
|
|
22
|
+
|
|
23
|
+
const isRedundant =
|
|
24
|
+
redundantReductionParameters || redundantReductionFunctors;
|
|
25
|
+
|
|
26
|
+
if (isRedundant) throw new StopTraversalException();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@AutoScoped
|
|
31
|
+
export class UsesUnificationOperator extends ScopedVisitor {
|
|
32
|
+
visitUnifyOperation(node: UnifyOperation): void {
|
|
33
|
+
throw new StopTraversalException();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
@AutoScoped
|
|
37
|
+
export class UsesCut extends ScopedVisitor {
|
|
38
|
+
visitExist(node: Exist): void {
|
|
39
|
+
if (node.identifier.value === "!") throw new StopTraversalException();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
@AutoScoped
|
|
43
|
+
export class UsesFail extends ScopedVisitor {
|
|
44
|
+
visitExist(node: Exist): void {
|
|
45
|
+
if (node.identifier.value === "fail") throw new StopTraversalException();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const logicSmells: Record<string, VisitorConstructor> = {
|
|
50
|
+
HasRedundantReduction: HasRedundantReduction,
|
|
51
|
+
UsesCut: UsesCut,
|
|
52
|
+
UsesFail: UsesFail,
|
|
53
|
+
UsesUnificationOperator: UsesUnificationOperator,
|
|
54
|
+
};
|