@seljs/checker 1.0.0 → 1.0.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.
- package/CHANGELOG.md +7 -0
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/checker/checker.cjs +482 -0
- package/dist/checker/checker.d.cts +176 -0
- package/dist/checker/checker.d.mts +176 -0
- package/dist/checker/checker.mjs +481 -0
- package/dist/checker/diagnostics.cjs +66 -0
- package/dist/checker/diagnostics.mjs +66 -0
- package/dist/checker/index.cjs +2 -0
- package/dist/checker/index.d.mts +2 -0
- package/dist/checker/index.mjs +3 -0
- package/dist/checker/type-compatibility.cjs +70 -0
- package/dist/checker/type-compatibility.d.cts +8 -0
- package/dist/checker/type-compatibility.d.mts +8 -0
- package/dist/checker/type-compatibility.mjs +69 -0
- package/dist/constants.cjs +14 -0
- package/dist/constants.d.cts +7 -0
- package/dist/constants.d.mts +7 -0
- package/dist/constants.mjs +13 -0
- package/dist/debug.cjs +7 -0
- package/dist/debug.mjs +5 -0
- package/dist/environment/codec-registry.cjs +109 -0
- package/dist/environment/codec-registry.d.cts +45 -0
- package/dist/environment/codec-registry.d.mts +45 -0
- package/dist/environment/codec-registry.mjs +108 -0
- package/dist/environment/hydrate.cjs +163 -0
- package/dist/environment/hydrate.d.cts +52 -0
- package/dist/environment/hydrate.d.mts +52 -0
- package/dist/environment/hydrate.mjs +160 -0
- package/dist/environment/index.cjs +4 -0
- package/dist/environment/index.d.mts +4 -0
- package/dist/environment/index.mjs +5 -0
- package/dist/environment/register-types.cjs +107 -0
- package/dist/environment/register-types.d.cts +15 -0
- package/dist/environment/register-types.d.mts +15 -0
- package/dist/environment/register-types.mjs +106 -0
- package/dist/environment/value-wrappers.cjs +44 -0
- package/dist/environment/value-wrappers.d.cts +20 -0
- package/dist/environment/value-wrappers.d.mts +20 -0
- package/dist/environment/value-wrappers.mjs +41 -0
- package/dist/index.cjs +27 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +13 -0
- package/dist/rules/defaults/deferred-call.cjs +107 -0
- package/dist/rules/defaults/deferred-call.mjs +106 -0
- package/dist/rules/defaults/index.cjs +6 -0
- package/dist/rules/defaults/index.mjs +7 -0
- package/dist/rules/defaults/no-constant-condition.cjs +31 -0
- package/dist/rules/defaults/no-constant-condition.mjs +31 -0
- package/dist/rules/defaults/no-mixed-operators.cjs +39 -0
- package/dist/rules/defaults/no-mixed-operators.mjs +39 -0
- package/dist/rules/defaults/no-redundant-bool.cjs +26 -0
- package/dist/rules/defaults/no-redundant-bool.mjs +26 -0
- package/dist/rules/defaults/no-self-comparison.cjs +44 -0
- package/dist/rules/defaults/no-self-comparison.mjs +43 -0
- package/dist/rules/defaults/require-type.cjs +18 -0
- package/dist/rules/defaults/require-type.mjs +18 -0
- package/dist/rules/facade.cjs +31 -0
- package/dist/rules/facade.d.cts +20 -0
- package/dist/rules/facade.d.mts +20 -0
- package/dist/rules/facade.mjs +31 -0
- package/dist/rules/index.cjs +2 -0
- package/dist/rules/index.d.mts +3 -0
- package/dist/rules/index.mjs +3 -0
- package/dist/rules/runner.cjs +40 -0
- package/dist/rules/runner.d.cts +27 -0
- package/dist/rules/runner.d.mts +27 -0
- package/dist/rules/runner.mjs +40 -0
- package/dist/rules/types.d.cts +77 -0
- package/dist/rules/types.d.mts +77 -0
- package/dist/utils/ast-utils.cjs +164 -0
- package/dist/utils/ast-utils.mjs +162 -0
- package/package.json +24 -17
- package/dist/checker/checker.d.ts +0 -173
- package/dist/checker/checker.js +0 -567
- package/dist/checker/diagnostics.d.ts +0 -10
- package/dist/checker/diagnostics.js +0 -80
- package/dist/checker/index.d.ts +0 -2
- package/dist/checker/index.js +0 -2
- package/dist/checker/type-compatibility.d.ts +0 -16
- package/dist/checker/type-compatibility.js +0 -59
- package/dist/constants.d.ts +0 -4
- package/dist/constants.js +0 -10
- package/dist/debug.d.ts +0 -2
- package/dist/debug.js +0 -2
- package/dist/environment/codec-registry.d.ts +0 -42
- package/dist/environment/codec-registry.js +0 -146
- package/dist/environment/hydrate.d.ts +0 -48
- package/dist/environment/hydrate.js +0 -198
- package/dist/environment/index.d.ts +0 -4
- package/dist/environment/index.js +0 -4
- package/dist/environment/register-types.d.ts +0 -14
- package/dist/environment/register-types.js +0 -154
- package/dist/environment/value-wrappers.d.ts +0 -17
- package/dist/environment/value-wrappers.js +0 -65
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -4
- package/dist/rules/defaults/deferred-call.d.ts +0 -13
- package/dist/rules/defaults/deferred-call.js +0 -162
- package/dist/rules/defaults/index.d.ts +0 -6
- package/dist/rules/defaults/index.js +0 -6
- package/dist/rules/defaults/no-constant-condition.d.ts +0 -7
- package/dist/rules/defaults/no-constant-condition.js +0 -36
- package/dist/rules/defaults/no-mixed-operators.d.ts +0 -9
- package/dist/rules/defaults/no-mixed-operators.js +0 -44
- package/dist/rules/defaults/no-redundant-bool.d.ts +0 -5
- package/dist/rules/defaults/no-redundant-bool.js +0 -27
- package/dist/rules/defaults/no-self-comparison.d.ts +0 -9
- package/dist/rules/defaults/no-self-comparison.js +0 -31
- package/dist/rules/defaults/require-type.d.ts +0 -7
- package/dist/rules/defaults/require-type.js +0 -19
- package/dist/rules/facade.d.ts +0 -22
- package/dist/rules/facade.js +0 -29
- package/dist/rules/index.d.ts +0 -3
- package/dist/rules/index.js +0 -3
- package/dist/rules/runner.d.ts +0 -16
- package/dist/rules/runner.js +0 -30
- package/dist/rules/types.d.ts +0 -73
- package/dist/rules/types.js +0 -1
- package/dist/utils/ast-utils.d.ts +0 -55
- package/dist/utils/ast-utils.js +0 -255
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { SOLIDITY_TYPES, SolidityIntTypeWrapper, formatUnitsValue, parseUnitsValue, toAddress, toBigInt } from "@seljs/types";
|
|
2
|
+
//#region src/environment/register-types.ts
|
|
3
|
+
/**
|
|
4
|
+
* Try to register an operator, silently ignoring "already registered" errors.
|
|
5
|
+
* cel-js auto-derives some cross-type operators (e.g., registering
|
|
6
|
+
* `sol_int == int` also creates `int == sol_int`).
|
|
7
|
+
*/
|
|
8
|
+
const tryRegisterOperator = (env, signature, handler) => {
|
|
9
|
+
try {
|
|
10
|
+
env.registerOperator(signature, handler);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (!(error instanceof Error) || !error.message.includes("already registered")) throw error;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const registerAddressOperators = (env) => {
|
|
16
|
+
env.registerOperator("sol_address == sol_address", (left, right) => toAddress(left) === toAddress(right));
|
|
17
|
+
env.registerOperator("sol_address < sol_address", (left, right) => toAddress(left) < toAddress(right));
|
|
18
|
+
env.registerOperator("sol_address <= sol_address", (left, right) => toAddress(left) <= toAddress(right));
|
|
19
|
+
env.registerOperator("sol_address > sol_address", (left, right) => toAddress(left) > toAddress(right));
|
|
20
|
+
env.registerOperator("sol_address >= sol_address", (left, right) => toAddress(left) >= toAddress(right));
|
|
21
|
+
};
|
|
22
|
+
const toSolInt = (value) => new SolidityIntTypeWrapper(value);
|
|
23
|
+
const registerIntegerOperators = (env) => {
|
|
24
|
+
env.registerOperator("sol_int + sol_int", (a, b) => toSolInt(toBigInt(a) + toBigInt(b)));
|
|
25
|
+
env.registerOperator("sol_int - sol_int", (a, b) => toSolInt(toBigInt(a) - toBigInt(b)));
|
|
26
|
+
env.registerOperator("sol_int * sol_int", (a, b) => toSolInt(toBigInt(a) * toBigInt(b)));
|
|
27
|
+
env.registerOperator("sol_int / sol_int", (a, b) => {
|
|
28
|
+
const divisor = toBigInt(b);
|
|
29
|
+
if (divisor === 0n) throw new Error("division by zero");
|
|
30
|
+
return toSolInt(toBigInt(a) / divisor);
|
|
31
|
+
});
|
|
32
|
+
env.registerOperator("sol_int % sol_int", (a, b) => {
|
|
33
|
+
const divisor = toBigInt(b);
|
|
34
|
+
if (divisor === 0n) throw new Error("modulo by zero");
|
|
35
|
+
return toSolInt(toBigInt(a) % divisor);
|
|
36
|
+
});
|
|
37
|
+
env.registerOperator("-sol_int", (a) => toSolInt(-toBigInt(a)));
|
|
38
|
+
env.registerOperator("sol_int == sol_int", (a, b) => toBigInt(a) === toBigInt(b));
|
|
39
|
+
env.registerOperator("sol_int < sol_int", (a, b) => toBigInt(a) < toBigInt(b));
|
|
40
|
+
env.registerOperator("sol_int <= sol_int", (a, b) => toBigInt(a) <= toBigInt(b));
|
|
41
|
+
env.registerOperator("sol_int > sol_int", (a, b) => toBigInt(a) > toBigInt(b));
|
|
42
|
+
env.registerOperator("sol_int >= sol_int", (a, b) => toBigInt(a) >= toBigInt(b));
|
|
43
|
+
tryRegisterOperator(env, "sol_int + int", (a, b) => toSolInt(toBigInt(a) + toBigInt(b)));
|
|
44
|
+
tryRegisterOperator(env, "sol_int - int", (a, b) => toSolInt(toBigInt(a) - toBigInt(b)));
|
|
45
|
+
tryRegisterOperator(env, "sol_int * int", (a, b) => toSolInt(toBigInt(a) * toBigInt(b)));
|
|
46
|
+
tryRegisterOperator(env, "sol_int / int", (a, b) => {
|
|
47
|
+
const divisor = toBigInt(b);
|
|
48
|
+
if (divisor === 0n) throw new Error("division by zero");
|
|
49
|
+
return toSolInt(toBigInt(a) / divisor);
|
|
50
|
+
});
|
|
51
|
+
tryRegisterOperator(env, "sol_int % int", (a, b) => {
|
|
52
|
+
const divisor = toBigInt(b);
|
|
53
|
+
if (divisor === 0n) throw new Error("modulo by zero");
|
|
54
|
+
return toSolInt(toBigInt(a) % divisor);
|
|
55
|
+
});
|
|
56
|
+
tryRegisterOperator(env, "sol_int == int", (a, b) => toBigInt(a) === toBigInt(b));
|
|
57
|
+
tryRegisterOperator(env, "int == sol_int", (a, b) => toBigInt(a) === toBigInt(b));
|
|
58
|
+
tryRegisterOperator(env, "sol_int < int", (a, b) => toBigInt(a) < toBigInt(b));
|
|
59
|
+
tryRegisterOperator(env, "int < sol_int", (a, b) => toBigInt(a) < toBigInt(b));
|
|
60
|
+
tryRegisterOperator(env, "sol_int <= int", (a, b) => toBigInt(a) <= toBigInt(b));
|
|
61
|
+
tryRegisterOperator(env, "int <= sol_int", (a, b) => toBigInt(a) <= toBigInt(b));
|
|
62
|
+
tryRegisterOperator(env, "sol_int > int", (a, b) => toBigInt(a) > toBigInt(b));
|
|
63
|
+
tryRegisterOperator(env, "int > sol_int", (a, b) => toBigInt(a) > toBigInt(b));
|
|
64
|
+
tryRegisterOperator(env, "sol_int >= int", (a, b) => toBigInt(a) >= toBigInt(b));
|
|
65
|
+
tryRegisterOperator(env, "int >= sol_int", (a, b) => toBigInt(a) >= toBigInt(b));
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Register all Solidity primitive types on a CEL Environment.
|
|
69
|
+
*/
|
|
70
|
+
const registerSolidityTypes = (env) => {
|
|
71
|
+
for (const entry of SOLIDITY_TYPES) {
|
|
72
|
+
if (entry.type === "builtin") continue;
|
|
73
|
+
env.registerType(entry.name, entry.wrapperClass);
|
|
74
|
+
if (entry.name === "sol_address") registerAddressOperators(env);
|
|
75
|
+
if (entry.name === "sol_int") registerIntegerOperators(env);
|
|
76
|
+
const Ctor = entry.wrapperClass;
|
|
77
|
+
for (const sig of entry.castSignatures) env.registerFunction(sig, (value) => new Ctor(value));
|
|
78
|
+
}
|
|
79
|
+
env.registerFunction("parseUnits(string, int): sol_int", (value, decimals) => parseUnitsValue(value, Number(toBigInt(decimals))));
|
|
80
|
+
env.registerFunction("parseUnits(int, int): sol_int", (value, decimals) => parseUnitsValue(value, Number(toBigInt(decimals))));
|
|
81
|
+
env.registerFunction("parseUnits(double, int): sol_int", (value, decimals) => parseUnitsValue(value, Number(toBigInt(decimals))));
|
|
82
|
+
env.registerFunction("parseUnits(sol_int, int): sol_int", (value, decimals) => parseUnitsValue(value, Number(toBigInt(decimals))));
|
|
83
|
+
env.registerFunction("formatUnits(sol_int, int): double", (value, decimals) => formatUnitsValue(value, Number(toBigInt(decimals))));
|
|
84
|
+
env.registerFunction("formatUnits(int, int): double", (value, decimals) => formatUnitsValue(value, Number(toBigInt(decimals))));
|
|
85
|
+
env.registerFunction("min(sol_int, sol_int): sol_int", (a, b) => toBigInt(a) <= toBigInt(b) ? toSolInt(a) : toSolInt(b));
|
|
86
|
+
env.registerFunction("min(sol_int, int): sol_int", (a, b) => toBigInt(a) <= toBigInt(b) ? toSolInt(a) : toSolInt(b));
|
|
87
|
+
env.registerFunction("min(int, sol_int): sol_int", (a, b) => toBigInt(a) <= toBigInt(b) ? toSolInt(a) : toSolInt(b));
|
|
88
|
+
env.registerFunction("min(int, int): sol_int", (a, b) => toBigInt(a) <= toBigInt(b) ? toSolInt(a) : toSolInt(b));
|
|
89
|
+
env.registerFunction("max(sol_int, sol_int): sol_int", (a, b) => toBigInt(a) >= toBigInt(b) ? toSolInt(a) : toSolInt(b));
|
|
90
|
+
env.registerFunction("max(sol_int, int): sol_int", (a, b) => toBigInt(a) >= toBigInt(b) ? toSolInt(a) : toSolInt(b));
|
|
91
|
+
env.registerFunction("max(int, sol_int): sol_int", (a, b) => toBigInt(a) >= toBigInt(b) ? toSolInt(a) : toSolInt(b));
|
|
92
|
+
env.registerFunction("max(int, int): sol_int", (a, b) => toBigInt(a) >= toBigInt(b) ? toSolInt(a) : toSolInt(b));
|
|
93
|
+
env.registerFunction("abs(sol_int): sol_int", (a) => {
|
|
94
|
+
const v = toBigInt(a);
|
|
95
|
+
return toSolInt(v < 0n ? -v : v);
|
|
96
|
+
});
|
|
97
|
+
env.registerFunction("abs(int): sol_int", (a) => {
|
|
98
|
+
const v = toBigInt(a);
|
|
99
|
+
return toSolInt(v < 0n ? -v : v);
|
|
100
|
+
});
|
|
101
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
102
|
+
env.registerFunction("isZeroAddress(sol_address): bool", (addr) => toAddress(addr) === ZERO_ADDRESS);
|
|
103
|
+
env.registerFunction("isZeroAddress(string): bool", (addr) => toAddress(addr) === ZERO_ADDRESS);
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
export { registerSolidityTypes };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require("../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let _seljs_types = require("@seljs/types");
|
|
3
|
+
//#region src/environment/value-wrappers.ts
|
|
4
|
+
const wrapFieldValue = (registry, celType, value) => {
|
|
5
|
+
const nested = registry.get(celType);
|
|
6
|
+
if (nested) return wrapStructValue(registry, nested, value);
|
|
7
|
+
return wrapValueForSel(celType, value);
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Maps a CEL type to the native CEL literal type for dual function registration.
|
|
11
|
+
*/
|
|
12
|
+
const toCelLiteralType = (celType) => {
|
|
13
|
+
if (celType === "sol_int") return "int";
|
|
14
|
+
if (celType === "sol_address") return "string";
|
|
15
|
+
return null;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Wraps a raw value into the corresponding SEL wrapper type.
|
|
19
|
+
*/
|
|
20
|
+
const wrapValueForSel = (celType, value) => {
|
|
21
|
+
if (value instanceof _seljs_types.SolidityAddressTypeWrapper) return value;
|
|
22
|
+
if (value instanceof _seljs_types.SolidityIntTypeWrapper) return value;
|
|
23
|
+
if (celType === "sol_address") return new _seljs_types.SolidityAddressTypeWrapper(value);
|
|
24
|
+
if (typeof value === "bigint") return new _seljs_types.SolidityIntTypeWrapper(value);
|
|
25
|
+
if (typeof value === "number" && Number.isInteger(value)) return new _seljs_types.SolidityIntTypeWrapper(BigInt(value));
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Recursively wraps a raw struct value with the correct ctor and wrapped field values.
|
|
30
|
+
*/
|
|
31
|
+
const wrapStructValue = (registry, info, value) => {
|
|
32
|
+
const { ctor, fieldNames, fieldTypes } = info;
|
|
33
|
+
let data;
|
|
34
|
+
if (Array.isArray(value)) data = Object.fromEntries(fieldNames.map((name, i) => [name, wrapFieldValue(registry, fieldTypes[name] ?? "dyn", value[i])]));
|
|
35
|
+
else {
|
|
36
|
+
const raw = value;
|
|
37
|
+
data = Object.fromEntries(Object.entries(raw).map(([name, val]) => [name, fieldTypes[name] ? wrapFieldValue(registry, fieldTypes[name], val) : val]));
|
|
38
|
+
}
|
|
39
|
+
return Object.assign(new ctor(), data);
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
exports.toCelLiteralType = toCelLiteralType;
|
|
43
|
+
exports.wrapStructValue = wrapStructValue;
|
|
44
|
+
exports.wrapValueForSel = wrapValueForSel;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/environment/value-wrappers.d.ts
|
|
2
|
+
interface StructInfo {
|
|
3
|
+
ctor: new () => object;
|
|
4
|
+
fieldNames: string[];
|
|
5
|
+
fieldTypes: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Maps a CEL type to the native CEL literal type for dual function registration.
|
|
9
|
+
*/
|
|
10
|
+
declare const toCelLiteralType: (celType: string) => string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Wraps a raw value into the corresponding SEL wrapper type.
|
|
13
|
+
*/
|
|
14
|
+
declare const wrapValueForSel: (celType: string, value: unknown) => unknown;
|
|
15
|
+
/**
|
|
16
|
+
* Recursively wraps a raw struct value with the correct ctor and wrapped field values.
|
|
17
|
+
*/
|
|
18
|
+
declare const wrapStructValue: (registry: Map<string, StructInfo>, info: StructInfo, value: unknown) => unknown;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { StructInfo, toCelLiteralType, wrapStructValue, wrapValueForSel };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/environment/value-wrappers.d.ts
|
|
2
|
+
interface StructInfo {
|
|
3
|
+
ctor: new () => object;
|
|
4
|
+
fieldNames: string[];
|
|
5
|
+
fieldTypes: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Maps a CEL type to the native CEL literal type for dual function registration.
|
|
9
|
+
*/
|
|
10
|
+
declare const toCelLiteralType: (celType: string) => string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Wraps a raw value into the corresponding SEL wrapper type.
|
|
13
|
+
*/
|
|
14
|
+
declare const wrapValueForSel: (celType: string, value: unknown) => unknown;
|
|
15
|
+
/**
|
|
16
|
+
* Recursively wraps a raw struct value with the correct ctor and wrapped field values.
|
|
17
|
+
*/
|
|
18
|
+
declare const wrapStructValue: (registry: Map<string, StructInfo>, info: StructInfo, value: unknown) => unknown;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { StructInfo, toCelLiteralType, wrapStructValue, wrapValueForSel };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SolidityAddressTypeWrapper, SolidityIntTypeWrapper } from "@seljs/types";
|
|
2
|
+
//#region src/environment/value-wrappers.ts
|
|
3
|
+
const wrapFieldValue = (registry, celType, value) => {
|
|
4
|
+
const nested = registry.get(celType);
|
|
5
|
+
if (nested) return wrapStructValue(registry, nested, value);
|
|
6
|
+
return wrapValueForSel(celType, value);
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Maps a CEL type to the native CEL literal type for dual function registration.
|
|
10
|
+
*/
|
|
11
|
+
const toCelLiteralType = (celType) => {
|
|
12
|
+
if (celType === "sol_int") return "int";
|
|
13
|
+
if (celType === "sol_address") return "string";
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Wraps a raw value into the corresponding SEL wrapper type.
|
|
18
|
+
*/
|
|
19
|
+
const wrapValueForSel = (celType, value) => {
|
|
20
|
+
if (value instanceof SolidityAddressTypeWrapper) return value;
|
|
21
|
+
if (value instanceof SolidityIntTypeWrapper) return value;
|
|
22
|
+
if (celType === "sol_address") return new SolidityAddressTypeWrapper(value);
|
|
23
|
+
if (typeof value === "bigint") return new SolidityIntTypeWrapper(value);
|
|
24
|
+
if (typeof value === "number" && Number.isInteger(value)) return new SolidityIntTypeWrapper(BigInt(value));
|
|
25
|
+
return value;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Recursively wraps a raw struct value with the correct ctor and wrapped field values.
|
|
29
|
+
*/
|
|
30
|
+
const wrapStructValue = (registry, info, value) => {
|
|
31
|
+
const { ctor, fieldNames, fieldTypes } = info;
|
|
32
|
+
let data;
|
|
33
|
+
if (Array.isArray(value)) data = Object.fromEntries(fieldNames.map((name, i) => [name, wrapFieldValue(registry, fieldTypes[name] ?? "dyn", value[i])]));
|
|
34
|
+
else {
|
|
35
|
+
const raw = value;
|
|
36
|
+
data = Object.fromEntries(Object.entries(raw).map(([name, val]) => [name, fieldTypes[name] ? wrapFieldValue(registry, fieldTypes[name], val) : val]));
|
|
37
|
+
}
|
|
38
|
+
return Object.assign(new ctor(), data);
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { toCelLiteralType, wrapStructValue, wrapValueForSel };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_type_compatibility = require("./checker/type-compatibility.cjs");
|
|
3
|
+
const require_codec_registry = require("./environment/codec-registry.cjs");
|
|
4
|
+
const require_register_types = require("./environment/register-types.cjs");
|
|
5
|
+
const require_value_wrappers = require("./environment/value-wrappers.cjs");
|
|
6
|
+
const require_hydrate = require("./environment/hydrate.cjs");
|
|
7
|
+
const require_constants = require("./constants.cjs");
|
|
8
|
+
const require_facade = require("./rules/facade.cjs");
|
|
9
|
+
const require_runner = require("./rules/runner.cjs");
|
|
10
|
+
require("./rules/index.cjs");
|
|
11
|
+
const require_checker = require("./checker/checker.cjs");
|
|
12
|
+
require("./checker/index.cjs");
|
|
13
|
+
require("./environment/index.cjs");
|
|
14
|
+
exports.COMPREHENSION_MACROS = require_constants.COMPREHENSION_MACROS;
|
|
15
|
+
exports.CelCodecRegistry = require_codec_registry.CelCodecRegistry;
|
|
16
|
+
exports.SCALAR_WRAPPER_FUNCTIONS = require_constants.SCALAR_WRAPPER_FUNCTIONS;
|
|
17
|
+
exports.SELChecker = require_checker.SELChecker;
|
|
18
|
+
exports.createBaseEnvironment = require_hydrate.createBaseEnvironment;
|
|
19
|
+
exports.createCheckerEnvironment = require_hydrate.createCheckerEnvironment;
|
|
20
|
+
exports.createRuntimeEnvironment = require_hydrate.createRuntimeEnvironment;
|
|
21
|
+
exports.isTypeCompatible = require_type_compatibility.isTypeCompatible;
|
|
22
|
+
exports.registerSolidityTypes = require_register_types.registerSolidityTypes;
|
|
23
|
+
exports.rules = require_facade.rules;
|
|
24
|
+
exports.runRules = require_runner.runRules;
|
|
25
|
+
exports.toCelLiteralType = require_value_wrappers.toCelLiteralType;
|
|
26
|
+
exports.wrapStructValue = require_value_wrappers.wrapStructValue;
|
|
27
|
+
exports.wrapValueForSel = require_value_wrappers.wrapValueForSel;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { RuleContext, RuleSeverity, RuleTier, SELRule } from "./rules/types.cjs";
|
|
2
|
+
import { rules } from "./rules/facade.cjs";
|
|
3
|
+
import { RunRulesOptions, runRules } from "./rules/runner.cjs";
|
|
4
|
+
import { CompletionItem, SELChecker, SELCheckerOptions, SELDiagnostic } from "./checker/checker.cjs";
|
|
5
|
+
import { isTypeCompatible } from "./checker/type-compatibility.cjs";
|
|
6
|
+
import { COMPREHENSION_MACROS, SCALAR_WRAPPER_FUNCTIONS } from "./constants.cjs";
|
|
7
|
+
import { CelCodecRegistry, CelCodecRegistryOptions, StructCodecDescriptor } from "./environment/codec-registry.cjs";
|
|
8
|
+
import { CelLimits, ContractCallHandler, RuntimeEnvironmentResult, createBaseEnvironment, createCheckerEnvironment, createRuntimeEnvironment } from "./environment/hydrate.cjs";
|
|
9
|
+
import { registerSolidityTypes } from "./environment/register-types.cjs";
|
|
10
|
+
import { StructInfo, toCelLiteralType, wrapStructValue, wrapValueForSel } from "./environment/value-wrappers.cjs";
|
|
11
|
+
export { COMPREHENSION_MACROS, CelCodecRegistry, CelCodecRegistryOptions, CelLimits, CompletionItem, ContractCallHandler, RuleContext, RuleSeverity, RuleTier, RunRulesOptions, RuntimeEnvironmentResult, SCALAR_WRAPPER_FUNCTIONS, SELChecker, SELCheckerOptions, SELDiagnostic, SELRule, StructCodecDescriptor, StructInfo, createBaseEnvironment, createCheckerEnvironment, createRuntimeEnvironment, isTypeCompatible, registerSolidityTypes, rules, runRules, toCelLiteralType, wrapStructValue, wrapValueForSel };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { RuleContext, RuleSeverity, RuleTier, SELRule } from "./rules/types.mjs";
|
|
2
|
+
import { rules } from "./rules/facade.mjs";
|
|
3
|
+
import { RunRulesOptions, runRules } from "./rules/runner.mjs";
|
|
4
|
+
import { CompletionItem, SELChecker, SELCheckerOptions, SELDiagnostic } from "./checker/checker.mjs";
|
|
5
|
+
import { isTypeCompatible } from "./checker/type-compatibility.mjs";
|
|
6
|
+
import { COMPREHENSION_MACROS, SCALAR_WRAPPER_FUNCTIONS } from "./constants.mjs";
|
|
7
|
+
import { CelCodecRegistry, CelCodecRegistryOptions, StructCodecDescriptor } from "./environment/codec-registry.mjs";
|
|
8
|
+
import { CelLimits, ContractCallHandler, RuntimeEnvironmentResult, createBaseEnvironment, createCheckerEnvironment, createRuntimeEnvironment } from "./environment/hydrate.mjs";
|
|
9
|
+
import { registerSolidityTypes } from "./environment/register-types.mjs";
|
|
10
|
+
import { StructInfo, toCelLiteralType, wrapStructValue, wrapValueForSel } from "./environment/value-wrappers.mjs";
|
|
11
|
+
export { COMPREHENSION_MACROS, CelCodecRegistry, CelCodecRegistryOptions, CelLimits, CompletionItem, ContractCallHandler, RuleContext, RuleSeverity, RuleTier, RunRulesOptions, RuntimeEnvironmentResult, SCALAR_WRAPPER_FUNCTIONS, SELChecker, SELCheckerOptions, SELDiagnostic, SELRule, StructCodecDescriptor, StructInfo, createBaseEnvironment, createCheckerEnvironment, createRuntimeEnvironment, isTypeCompatible, registerSolidityTypes, rules, runRules, toCelLiteralType, wrapStructValue, wrapValueForSel };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isTypeCompatible } from "./checker/type-compatibility.mjs";
|
|
2
|
+
import { CelCodecRegistry } from "./environment/codec-registry.mjs";
|
|
3
|
+
import { registerSolidityTypes } from "./environment/register-types.mjs";
|
|
4
|
+
import { toCelLiteralType, wrapStructValue, wrapValueForSel } from "./environment/value-wrappers.mjs";
|
|
5
|
+
import { createBaseEnvironment, createCheckerEnvironment, createRuntimeEnvironment } from "./environment/hydrate.mjs";
|
|
6
|
+
import { COMPREHENSION_MACROS, SCALAR_WRAPPER_FUNCTIONS } from "./constants.mjs";
|
|
7
|
+
import { rules } from "./rules/facade.mjs";
|
|
8
|
+
import { runRules } from "./rules/runner.mjs";
|
|
9
|
+
import "./rules/index.mjs";
|
|
10
|
+
import { SELChecker } from "./checker/checker.mjs";
|
|
11
|
+
import "./checker/index.mjs";
|
|
12
|
+
import "./environment/index.mjs";
|
|
13
|
+
export { COMPREHENSION_MACROS, CelCodecRegistry, SCALAR_WRAPPER_FUNCTIONS, SELChecker, createBaseEnvironment, createCheckerEnvironment, createRuntimeEnvironment, isTypeCompatible, registerSolidityTypes, rules, runRules, toCelLiteralType, wrapStructValue, wrapValueForSel };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_constants = require("../../constants.cjs");
|
|
3
|
+
const require_ast_utils = require("../../utils/ast-utils.cjs");
|
|
4
|
+
let _seljs_common = require("@seljs/common");
|
|
5
|
+
//#region src/rules/defaults/deferred-call.ts
|
|
6
|
+
/**
|
|
7
|
+
* Checks whether a scalar wrapper inner value (inside solInt/solAddress) is resolvable.
|
|
8
|
+
*/
|
|
9
|
+
const isScalarInnerResolvable = (inner, scopedVars) => {
|
|
10
|
+
if (!(0, _seljs_common.isAstNode)(inner)) return false;
|
|
11
|
+
if (inner.op === "value") return true;
|
|
12
|
+
return inner.op === "id" && typeof inner.args === "string" && !scopedVars.has(inner.args);
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Checks whether a call node is a resolvable scalar wrapper (solInt/solAddress).
|
|
16
|
+
*/
|
|
17
|
+
const isResolvableScalarWrapper = (node, scopedVars) => {
|
|
18
|
+
const nodeArgs = node.args;
|
|
19
|
+
const fnName = nodeArgs[0];
|
|
20
|
+
const fnArgs = nodeArgs[1];
|
|
21
|
+
if (typeof fnName !== "string" || !require_constants.SCALAR_WRAPPER_FUNCTIONS.has(fnName) || !Array.isArray(fnArgs) || fnArgs.length !== 1) return false;
|
|
22
|
+
return isScalarInnerResolvable(fnArgs[0], scopedVars);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Checks whether a single argument node is statically resolvable
|
|
26
|
+
* (literal, context variable, or nested contract call result).
|
|
27
|
+
*/
|
|
28
|
+
const isResolvableArg = (argNode, contractNames, scopedVars) => {
|
|
29
|
+
if (!(0, _seljs_common.isAstNode)(argNode)) return false;
|
|
30
|
+
if (argNode.op === "call") return isResolvableScalarWrapper(argNode, scopedVars);
|
|
31
|
+
if (argNode.op === "value") return true;
|
|
32
|
+
if (argNode.op === "id" && typeof argNode.args === "string") return !scopedVars.has(argNode.args);
|
|
33
|
+
if (argNode.op === "rcall") return isContractRCall(argNode, contractNames);
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Checks whether an rcall node targets a registered contract.
|
|
38
|
+
*/
|
|
39
|
+
const isContractRCall = (node, contractNames) => {
|
|
40
|
+
const receiverNode = node.args[1];
|
|
41
|
+
return (0, _seljs_common.isAstNode)(receiverNode) && receiverNode.op === "id" && typeof receiverNode.args === "string" && contractNames.has(receiverNode.args);
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Extracts the rcall components: method name, receiver node, and argument list.
|
|
45
|
+
*/
|
|
46
|
+
const parseRCallNode = (node) => {
|
|
47
|
+
const nodeArgs = node.args;
|
|
48
|
+
const method = nodeArgs[0];
|
|
49
|
+
const receiver = nodeArgs[1];
|
|
50
|
+
const callArgs = nodeArgs[2];
|
|
51
|
+
if (typeof method !== "string" || !Array.isArray(callArgs)) return;
|
|
52
|
+
return {
|
|
53
|
+
method,
|
|
54
|
+
receiver,
|
|
55
|
+
callArgs
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Tracks scoped variables introduced by comprehension macros or cel.bind().
|
|
60
|
+
*/
|
|
61
|
+
const trackScopedVars = (parsed, scopedVars) => {
|
|
62
|
+
const { method, receiver, callArgs } = parsed;
|
|
63
|
+
if ((0, _seljs_common.isAstNode)(receiver) && receiver.op !== "id" && require_constants.COMPREHENSION_MACROS.has(method) && callArgs.length >= 1) {
|
|
64
|
+
const iterVarNode = callArgs[0];
|
|
65
|
+
if ((0, _seljs_common.isAstNode)(iterVarNode) && iterVarNode.op === "id" && typeof iterVarNode.args === "string") scopedVars.add(iterVarNode.args);
|
|
66
|
+
}
|
|
67
|
+
if (method === "bind" && (0, _seljs_common.isAstNode)(receiver) && receiver.op === "id" && receiver.args === "cel" && callArgs.length >= 1) {
|
|
68
|
+
const nameNode = callArgs[0];
|
|
69
|
+
if ((0, _seljs_common.isAstNode)(nameNode) && nameNode.op === "id" && typeof nameNode.args === "string") scopedVars.add(nameNode.args);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Flags contract calls whose arguments cannot be resolved statically,
|
|
74
|
+
* meaning they will execute as live RPC calls at evaluation time instead
|
|
75
|
+
* of being batched via multicall.
|
|
76
|
+
*
|
|
77
|
+
* This includes calls with arguments that depend on:
|
|
78
|
+
* - Comprehension iteration variables (map/filter/exists)
|
|
79
|
+
* - cel.bind() scoped variables
|
|
80
|
+
* - Arithmetic or other expressions on call results
|
|
81
|
+
* - Struct field access on call results
|
|
82
|
+
*/
|
|
83
|
+
const deferredCall = {
|
|
84
|
+
name: "deferred-call",
|
|
85
|
+
description: "Flags contract calls with dynamic arguments that cannot be batched via multicall.",
|
|
86
|
+
defaultSeverity: "info",
|
|
87
|
+
tier: "structural",
|
|
88
|
+
run(context) {
|
|
89
|
+
const contractNames = new Set(context.schema.contracts.map((c) => c.name));
|
|
90
|
+
if (contractNames.size === 0) return [];
|
|
91
|
+
const diagnostics = [];
|
|
92
|
+
const scopedVars = /* @__PURE__ */ new Set();
|
|
93
|
+
require_ast_utils.walkAST(context.ast, (node) => {
|
|
94
|
+
if (node.op !== "rcall") return;
|
|
95
|
+
const parsed = parseRCallNode(node);
|
|
96
|
+
if (!parsed) return;
|
|
97
|
+
const { method, callArgs } = parsed;
|
|
98
|
+
trackScopedVars(parsed, scopedVars);
|
|
99
|
+
if (!isContractRCall(node, contractNames)) return;
|
|
100
|
+
const contractName = node.args[1].args;
|
|
101
|
+
if (callArgs.some((argNode) => !isResolvableArg(argNode, contractNames, scopedVars))) diagnostics.push(context.report(node, `\`${contractName}.${method}()\` has dynamic arguments and will execute as a live RPC call instead of being batched.`));
|
|
102
|
+
});
|
|
103
|
+
return diagnostics;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
107
|
+
exports.deferredCall = deferredCall;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { COMPREHENSION_MACROS, SCALAR_WRAPPER_FUNCTIONS } from "../../constants.mjs";
|
|
2
|
+
import { walkAST } from "../../utils/ast-utils.mjs";
|
|
3
|
+
import { isAstNode } from "@seljs/common";
|
|
4
|
+
//#region src/rules/defaults/deferred-call.ts
|
|
5
|
+
/**
|
|
6
|
+
* Checks whether a scalar wrapper inner value (inside solInt/solAddress) is resolvable.
|
|
7
|
+
*/
|
|
8
|
+
const isScalarInnerResolvable = (inner, scopedVars) => {
|
|
9
|
+
if (!isAstNode(inner)) return false;
|
|
10
|
+
if (inner.op === "value") return true;
|
|
11
|
+
return inner.op === "id" && typeof inner.args === "string" && !scopedVars.has(inner.args);
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether a call node is a resolvable scalar wrapper (solInt/solAddress).
|
|
15
|
+
*/
|
|
16
|
+
const isResolvableScalarWrapper = (node, scopedVars) => {
|
|
17
|
+
const nodeArgs = node.args;
|
|
18
|
+
const fnName = nodeArgs[0];
|
|
19
|
+
const fnArgs = nodeArgs[1];
|
|
20
|
+
if (typeof fnName !== "string" || !SCALAR_WRAPPER_FUNCTIONS.has(fnName) || !Array.isArray(fnArgs) || fnArgs.length !== 1) return false;
|
|
21
|
+
return isScalarInnerResolvable(fnArgs[0], scopedVars);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Checks whether a single argument node is statically resolvable
|
|
25
|
+
* (literal, context variable, or nested contract call result).
|
|
26
|
+
*/
|
|
27
|
+
const isResolvableArg = (argNode, contractNames, scopedVars) => {
|
|
28
|
+
if (!isAstNode(argNode)) return false;
|
|
29
|
+
if (argNode.op === "call") return isResolvableScalarWrapper(argNode, scopedVars);
|
|
30
|
+
if (argNode.op === "value") return true;
|
|
31
|
+
if (argNode.op === "id" && typeof argNode.args === "string") return !scopedVars.has(argNode.args);
|
|
32
|
+
if (argNode.op === "rcall") return isContractRCall(argNode, contractNames);
|
|
33
|
+
return false;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Checks whether an rcall node targets a registered contract.
|
|
37
|
+
*/
|
|
38
|
+
const isContractRCall = (node, contractNames) => {
|
|
39
|
+
const receiverNode = node.args[1];
|
|
40
|
+
return isAstNode(receiverNode) && receiverNode.op === "id" && typeof receiverNode.args === "string" && contractNames.has(receiverNode.args);
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Extracts the rcall components: method name, receiver node, and argument list.
|
|
44
|
+
*/
|
|
45
|
+
const parseRCallNode = (node) => {
|
|
46
|
+
const nodeArgs = node.args;
|
|
47
|
+
const method = nodeArgs[0];
|
|
48
|
+
const receiver = nodeArgs[1];
|
|
49
|
+
const callArgs = nodeArgs[2];
|
|
50
|
+
if (typeof method !== "string" || !Array.isArray(callArgs)) return;
|
|
51
|
+
return {
|
|
52
|
+
method,
|
|
53
|
+
receiver,
|
|
54
|
+
callArgs
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Tracks scoped variables introduced by comprehension macros or cel.bind().
|
|
59
|
+
*/
|
|
60
|
+
const trackScopedVars = (parsed, scopedVars) => {
|
|
61
|
+
const { method, receiver, callArgs } = parsed;
|
|
62
|
+
if (isAstNode(receiver) && receiver.op !== "id" && COMPREHENSION_MACROS.has(method) && callArgs.length >= 1) {
|
|
63
|
+
const iterVarNode = callArgs[0];
|
|
64
|
+
if (isAstNode(iterVarNode) && iterVarNode.op === "id" && typeof iterVarNode.args === "string") scopedVars.add(iterVarNode.args);
|
|
65
|
+
}
|
|
66
|
+
if (method === "bind" && isAstNode(receiver) && receiver.op === "id" && receiver.args === "cel" && callArgs.length >= 1) {
|
|
67
|
+
const nameNode = callArgs[0];
|
|
68
|
+
if (isAstNode(nameNode) && nameNode.op === "id" && typeof nameNode.args === "string") scopedVars.add(nameNode.args);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Flags contract calls whose arguments cannot be resolved statically,
|
|
73
|
+
* meaning they will execute as live RPC calls at evaluation time instead
|
|
74
|
+
* of being batched via multicall.
|
|
75
|
+
*
|
|
76
|
+
* This includes calls with arguments that depend on:
|
|
77
|
+
* - Comprehension iteration variables (map/filter/exists)
|
|
78
|
+
* - cel.bind() scoped variables
|
|
79
|
+
* - Arithmetic or other expressions on call results
|
|
80
|
+
* - Struct field access on call results
|
|
81
|
+
*/
|
|
82
|
+
const deferredCall = {
|
|
83
|
+
name: "deferred-call",
|
|
84
|
+
description: "Flags contract calls with dynamic arguments that cannot be batched via multicall.",
|
|
85
|
+
defaultSeverity: "info",
|
|
86
|
+
tier: "structural",
|
|
87
|
+
run(context) {
|
|
88
|
+
const contractNames = new Set(context.schema.contracts.map((c) => c.name));
|
|
89
|
+
if (contractNames.size === 0) return [];
|
|
90
|
+
const diagnostics = [];
|
|
91
|
+
const scopedVars = /* @__PURE__ */ new Set();
|
|
92
|
+
walkAST(context.ast, (node) => {
|
|
93
|
+
if (node.op !== "rcall") return;
|
|
94
|
+
const parsed = parseRCallNode(node);
|
|
95
|
+
if (!parsed) return;
|
|
96
|
+
const { method, callArgs } = parsed;
|
|
97
|
+
trackScopedVars(parsed, scopedVars);
|
|
98
|
+
if (!isContractRCall(node, contractNames)) return;
|
|
99
|
+
const contractName = node.args[1].args;
|
|
100
|
+
if (callArgs.some((argNode) => !isResolvableArg(argNode, contractNames, scopedVars))) diagnostics.push(context.report(node, `\`${contractName}.${method}()\` has dynamic arguments and will execute as a live RPC call instead of being batched.`));
|
|
101
|
+
});
|
|
102
|
+
return diagnostics;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
export { deferredCall };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const require_ast_utils = require("../../utils/ast-utils.cjs");
|
|
2
|
+
//#region src/rules/defaults/no-constant-condition.ts
|
|
3
|
+
/**
|
|
4
|
+
* Flags conditions that are always true or always false:
|
|
5
|
+
* - `true && x`, `false || x` (boolean literal in logical operator)
|
|
6
|
+
* - Both sides are literal values in a comparison
|
|
7
|
+
*/
|
|
8
|
+
const noConstantCondition = {
|
|
9
|
+
name: "no-constant-condition",
|
|
10
|
+
description: "Disallow constant conditions in logical and comparison operators.",
|
|
11
|
+
defaultSeverity: "warning",
|
|
12
|
+
tier: "structural",
|
|
13
|
+
run(context) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
require_ast_utils.walkAST(context.ast, (node) => {
|
|
16
|
+
if (node.op === "&&" || node.op === "||") {
|
|
17
|
+
const [left, right] = node.args;
|
|
18
|
+
if (left.op === "value" && typeof left.args === "boolean") diagnostics.push(context.report(node, `Constant condition: left side of \`${node.op}\` is always \`${String(left.args)}\`.`));
|
|
19
|
+
if (right.op === "value" && typeof right.args === "boolean") diagnostics.push(context.report(node, `Constant condition: right side of \`${node.op}\` is always \`${String(right.args)}\`.`));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (node.op === "==" || node.op === "!=") {
|
|
23
|
+
const [left, right] = node.args;
|
|
24
|
+
if (left.op === "value" && right.op === "value") diagnostics.push(context.report(node, `Constant condition: both sides of \`${node.op}\` are literal values.`));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return diagnostics;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
exports.noConstantCondition = noConstantCondition;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { walkAST } from "../../utils/ast-utils.mjs";
|
|
2
|
+
//#region src/rules/defaults/no-constant-condition.ts
|
|
3
|
+
/**
|
|
4
|
+
* Flags conditions that are always true or always false:
|
|
5
|
+
* - `true && x`, `false || x` (boolean literal in logical operator)
|
|
6
|
+
* - Both sides are literal values in a comparison
|
|
7
|
+
*/
|
|
8
|
+
const noConstantCondition = {
|
|
9
|
+
name: "no-constant-condition",
|
|
10
|
+
description: "Disallow constant conditions in logical and comparison operators.",
|
|
11
|
+
defaultSeverity: "warning",
|
|
12
|
+
tier: "structural",
|
|
13
|
+
run(context) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
walkAST(context.ast, (node) => {
|
|
16
|
+
if (node.op === "&&" || node.op === "||") {
|
|
17
|
+
const [left, right] = node.args;
|
|
18
|
+
if (left.op === "value" && typeof left.args === "boolean") diagnostics.push(context.report(node, `Constant condition: left side of \`${node.op}\` is always \`${String(left.args)}\`.`));
|
|
19
|
+
if (right.op === "value" && typeof right.args === "boolean") diagnostics.push(context.report(node, `Constant condition: right side of \`${node.op}\` is always \`${String(right.args)}\`.`));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (node.op === "==" || node.op === "!=") {
|
|
23
|
+
const [left, right] = node.args;
|
|
24
|
+
if (left.op === "value" && right.op === "value") diagnostics.push(context.report(node, `Constant condition: both sides of \`${node.op}\` are literal values.`));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return diagnostics;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { noConstantCondition };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const require_ast_utils = require("../../utils/ast-utils.cjs");
|
|
2
|
+
//#region src/rules/defaults/no-mixed-operators.ts
|
|
3
|
+
/**
|
|
4
|
+
* Check whether a child expression has explicit parentheses in the source.
|
|
5
|
+
* Scans backwards from the child's start position for a `(`.
|
|
6
|
+
*/
|
|
7
|
+
const hasExplicitParens = (src, child) => {
|
|
8
|
+
let i = require_ast_utils.nodeSpan(child).from - 1;
|
|
9
|
+
while (i >= 0 && (src[i] === " " || src[i] === " ")) i--;
|
|
10
|
+
return i >= 0 && src[i] === "(";
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Flags mixed `&&` and `||` without explicit parentheses.
|
|
14
|
+
*
|
|
15
|
+
* The cel-js parser discards parentheses from the AST, so this rule
|
|
16
|
+
* inspects the original source text via `node.input` to determine
|
|
17
|
+
* whether parentheses were explicitly written.
|
|
18
|
+
*/
|
|
19
|
+
const noMixedOperators = {
|
|
20
|
+
name: "no-mixed-operators",
|
|
21
|
+
description: "Require parentheses when mixing logical operators (&& and ||).",
|
|
22
|
+
defaultSeverity: "info",
|
|
23
|
+
tier: "structural",
|
|
24
|
+
run(context) {
|
|
25
|
+
const diagnostics = [];
|
|
26
|
+
require_ast_utils.walkAST(context.ast, (node) => {
|
|
27
|
+
if (node.op !== "&&" && node.op !== "||") return;
|
|
28
|
+
const opposite = node.op === "&&" ? "||" : "&&";
|
|
29
|
+
const [left, right] = node.args;
|
|
30
|
+
for (const child of [left, right]) if (child.op === opposite && !hasExplicitParens(node.input, child)) {
|
|
31
|
+
diagnostics.push(context.report(node, "Mixed logical operators without parentheses. Add explicit grouping to clarify precedence."));
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return diagnostics;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
exports.noMixedOperators = noMixedOperators;
|