@xano/xanoscript-language-server 11.8.4 → 11.9.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/.claude/settings.local.json +2 -1
- package/cache/documentCache.js +58 -10
- package/lexer/comment.js +14 -24
- package/lexer/db.js +1 -2
- package/lexer/security.js +16 -0
- package/onCompletion/onCompletion.js +61 -1
- package/onDefinition/onDefinition.js +150 -0
- package/onDefinition/onDefinition.spec.js +313 -0
- package/onDidChangeContent/onDidChangeContent.js +53 -6
- package/onHover/functions.md +28 -0
- package/package.json +1 -1
- package/parser/base_parser.js +61 -3
- package/parser/clauses/middlewareClause.js +16 -0
- package/parser/definitions/columnDefinition.js +5 -0
- package/parser/functions/api/apiCallFn.js +5 -3
- package/parser/functions/controls/functionCallFn.js +5 -3
- package/parser/functions/controls/functionRunFn.js +61 -5
- package/parser/functions/controls/taskCallFn.js +5 -3
- package/parser/functions/db/captureFieldName.js +63 -0
- package/parser/functions/db/dbAddFn.js +5 -3
- package/parser/functions/db/dbAddOrEditFn.js +13 -3
- package/parser/functions/db/dbBulkAddFn.js +5 -3
- package/parser/functions/db/dbBulkDeleteFn.js +5 -3
- package/parser/functions/db/dbBulkPatchFn.js +5 -3
- package/parser/functions/db/dbBulkUpdateFn.js +5 -3
- package/parser/functions/db/dbDelFn.js +10 -3
- package/parser/functions/db/dbEditFn.js +13 -3
- package/parser/functions/db/dbGetFn.js +10 -3
- package/parser/functions/db/dbHasFn.js +9 -3
- package/parser/functions/db/dbPatchFn.js +10 -3
- package/parser/functions/db/dbQueryFn.js +29 -3
- package/parser/functions/db/dbSchemaFn.js +5 -3
- package/parser/functions/db/dbTruncateFn.js +5 -3
- package/parser/functions/middlewareCallFn.js +3 -1
- package/parser/functions/security/register.js +19 -9
- package/parser/functions/security/securityCreateAuthTokenFn.js +22 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.spec.js +26 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.spec.js +25 -0
- package/parser/functions/securityFn.js +2 -0
- package/parser/functions/varFn.js +1 -1
- package/parser/generic/asVariable.js +2 -0
- package/parser/generic/assignableVariableAs.js +1 -0
- package/parser/generic/assignableVariableProperty.js +5 -2
- package/parser/task_parser.js +2 -1
- package/parser/tests/task/valid_sources/create_leak.xs +165 -0
- package/parser/tests/variable_test/coverage_check.xs +293 -0
- package/parser/variableScanner.js +64 -0
- package/parser/variableValidator.js +44 -0
- package/parser/variableValidator.spec.js +179 -0
- package/server.js +206 -18
- package/utils.js +32 -0
- package/utils.spec.js +93 -1
- package/workspace/crossFileValidator.js +166 -0
- package/workspace/crossFileValidator.spec.js +654 -0
- package/workspace/referenceTracking.spec.js +420 -0
- package/workspace/workspaceIndex.js +149 -0
- package/workspace/workspaceIndex.spec.js +189 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// Valide a couverture pour un assuré
|
|
2
|
+
query "coverage/check" verb=GET {
|
|
3
|
+
api_group = "coverage"
|
|
4
|
+
|
|
5
|
+
input {
|
|
6
|
+
// VIN (17 caractères)
|
|
7
|
+
text vin filters=trim|upper
|
|
8
|
+
|
|
9
|
+
// Code garage (optionnel)
|
|
10
|
+
text shop_code? filters=trim|upper
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
stack {
|
|
14
|
+
// Recherche et validation du véhicule
|
|
15
|
+
group {
|
|
16
|
+
stack {
|
|
17
|
+
// Trouve le vehicle a partir du VIN
|
|
18
|
+
db.query vehicle {
|
|
19
|
+
where = $db.vehicle.vin == $input.vin
|
|
20
|
+
return = {type: "single"}
|
|
21
|
+
mock = {
|
|
22
|
+
"devrait retourner véhicule sans police" : {id: 2, vin: "2HGCM82633A004353", make: "Toyota", model: "Camry", year: 2021, owner_name: "Marie Martin"}
|
|
23
|
+
"devrait échouer si VIN introuvable" : null
|
|
24
|
+
"devrait avoir coverages vide si pas de police": {id: 2, vin: "2HGCM82633A004353", make: "Toyota", model: "Camry", year: 2021, owner_name: "Marie Martin"}
|
|
25
|
+
}
|
|
26
|
+
} as $vehicle
|
|
27
|
+
|
|
28
|
+
// Véhicule requis
|
|
29
|
+
precondition ($vehicle != null) {
|
|
30
|
+
error_type = "notfound"
|
|
31
|
+
error = "Vehicle not found with VIN: " ~ $input.vin
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
var $unused_variable {
|
|
36
|
+
value = "This variable is not used anywhere in the code"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Réponse véhicule
|
|
40
|
+
var $vehicle_response {
|
|
41
|
+
value = {
|
|
42
|
+
vin : $vehicle.vin
|
|
43
|
+
make : $vehicle.make
|
|
44
|
+
model : $vehicle.model
|
|
45
|
+
year : $vehicle.year
|
|
46
|
+
owner_name: $vehicle.owner_name
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Recherche de la police d'assurance active
|
|
53
|
+
group {
|
|
54
|
+
stack {
|
|
55
|
+
// Police active du véhicule
|
|
56
|
+
db.query insurance_policy {
|
|
57
|
+
where = $db.insurance_policy.vehicle_id == $vehicle.id && $db.insurance_policy.status == "active"
|
|
58
|
+
return = {type: "single"}
|
|
59
|
+
mock = {
|
|
60
|
+
"devrait retourner véhicule sans police" : null
|
|
61
|
+
"devrait avoir coverages vide si pas de police": null
|
|
62
|
+
}
|
|
63
|
+
} as $policy
|
|
64
|
+
|
|
65
|
+
// Init réponses
|
|
66
|
+
var $policy_response {
|
|
67
|
+
value = null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var $coverages_response {
|
|
71
|
+
value = []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var $claims_summary {
|
|
75
|
+
value = {total_claims: 0, recent_claims: []}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Traitement si police active
|
|
81
|
+
conditional {
|
|
82
|
+
if ($policy != null) {
|
|
83
|
+
// Construction de la réponse police
|
|
84
|
+
group {
|
|
85
|
+
stack {
|
|
86
|
+
// Réponse police
|
|
87
|
+
var.update $policy_response {
|
|
88
|
+
value = {
|
|
89
|
+
policy_number : $policy.policy_number
|
|
90
|
+
status : $policy.status
|
|
91
|
+
effective_date : $policy.effective_date
|
|
92
|
+
expiration_date: $policy.expiration_date
|
|
93
|
+
insurer_name : $policy.insurer_name
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Récupération des couvertures
|
|
100
|
+
group {
|
|
101
|
+
stack {
|
|
102
|
+
db.query policy_coverage {
|
|
103
|
+
where = $db.policy_coverage.policy_id == $policy.id
|
|
104
|
+
return = {type: "list"}
|
|
105
|
+
} as $policy_coverages
|
|
106
|
+
|
|
107
|
+
// Requête par couverture
|
|
108
|
+
foreach ($policy_coverages) {
|
|
109
|
+
each as $pc {
|
|
110
|
+
db.get coverage_type {
|
|
111
|
+
field_name = "id"
|
|
112
|
+
field_value = $pc.coverage_type_id
|
|
113
|
+
} as $ctype
|
|
114
|
+
|
|
115
|
+
// Objet couverture
|
|
116
|
+
var $coverage_item {
|
|
117
|
+
value = {
|
|
118
|
+
code : $ctype.code
|
|
119
|
+
name : $ctype.name
|
|
120
|
+
coverage_limit: $pc.coverage_limit
|
|
121
|
+
deductible : $pc.deductible
|
|
122
|
+
is_active : $pc.is_active
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Ajout au tableau
|
|
127
|
+
array.push $coverages_response {
|
|
128
|
+
value = $coverage_item
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Récupération et filtrage des sinistres
|
|
136
|
+
group {
|
|
137
|
+
stack {
|
|
138
|
+
// Récupère TOUS les sinistres puis filtre en code
|
|
139
|
+
db.query claim {
|
|
140
|
+
where = $db.claim.policy_id == $policy.id
|
|
141
|
+
return = {type: "list"}
|
|
142
|
+
} as $all_claims
|
|
143
|
+
|
|
144
|
+
// Total sinistres
|
|
145
|
+
var $total_claims_count {
|
|
146
|
+
value = $all_claims|count
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Date il y a 2 ans
|
|
150
|
+
var $two_years_ago {
|
|
151
|
+
value = now|transform_timestamp:"-2 years"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Filtrage en code (au lieu de SQL)
|
|
155
|
+
var $recent_claims_list {
|
|
156
|
+
value = []
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
foreach ($all_claims) {
|
|
160
|
+
each as $claim {
|
|
161
|
+
// Date en timestamp
|
|
162
|
+
var $claim_date_ts {
|
|
163
|
+
value = $claim.incident_date|to_timestamp
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ⚠️ Filtre en code - devrait être en SQL
|
|
167
|
+
conditional {
|
|
168
|
+
if ($claim_date_ts >= $two_years_ago) {
|
|
169
|
+
var $recent_claim_item {
|
|
170
|
+
value = {
|
|
171
|
+
claim_number : $claim.claim_number
|
|
172
|
+
incident_date : $claim.incident_date
|
|
173
|
+
status : $claim.status
|
|
174
|
+
amount_claimed: $claim.amount_claimed
|
|
175
|
+
amount_paid : $claim.amount_paid
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
array.push $recent_claims_list {
|
|
180
|
+
value = $recent_claim_item
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Résumé sinistres
|
|
188
|
+
var.update $claims_summary {
|
|
189
|
+
value = {
|
|
190
|
+
total_claims : $total_claims_count
|
|
191
|
+
recent_claims: $recent_claims_list
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Recherche du garage si code fourni
|
|
200
|
+
group {
|
|
201
|
+
stack {
|
|
202
|
+
var $queried_by {
|
|
203
|
+
value = null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
conditional {
|
|
207
|
+
if ($input.shop_code != null && ($input.shop_code|strlen) > 0) {
|
|
208
|
+
// Récupère garage
|
|
209
|
+
db.query repair_shop {
|
|
210
|
+
where = $db.repair_shop.shop_code == $input.shop_code
|
|
211
|
+
return = {type: "single"}
|
|
212
|
+
} as $shop
|
|
213
|
+
|
|
214
|
+
// Si garage trouvé
|
|
215
|
+
conditional {
|
|
216
|
+
if ($shop != null) {
|
|
217
|
+
var.update $queried_by {
|
|
218
|
+
value = {shop_code: $shop.shop_code, shop_name: $shop.name}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Construction de la réponse finale
|
|
228
|
+
group {
|
|
229
|
+
stack {
|
|
230
|
+
var $result {
|
|
231
|
+
value = {
|
|
232
|
+
vehicle : $vehicle_response
|
|
233
|
+
policy : $policy_response
|
|
234
|
+
coverages : $coverages_response
|
|
235
|
+
claims_summary: $claims_summary
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Ajoute queried_by si présent
|
|
240
|
+
conditional {
|
|
241
|
+
if ($queried_by != null) {
|
|
242
|
+
var.update $result {
|
|
243
|
+
value = $result|set:"queried_by":$queried_by
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
response = $result
|
|
252
|
+
history = 100
|
|
253
|
+
|
|
254
|
+
test "devrait retourner véhicule sans police" {
|
|
255
|
+
input = {vin: "2HGCM82633A004353"}
|
|
256
|
+
|
|
257
|
+
expect.to_equal ($response.vehicle.vin) {
|
|
258
|
+
value = "2HGCM82633A004353"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
expect.to_equal ($response.vehicle.make) {
|
|
262
|
+
value = "Toyota"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
expect.to_be_null ($response.policy)
|
|
266
|
+
expect.to_be_empty ($response.coverages)
|
|
267
|
+
expect.to_equal ($response.claims_summary.total_claims) {
|
|
268
|
+
value = 0
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
test "devrait échouer si VIN introuvable" {
|
|
273
|
+
input = {vin: "INVALID12345678901"}
|
|
274
|
+
|
|
275
|
+
expect.to_throw {
|
|
276
|
+
exception = "ERROR_CODE_NOT_FOUND"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
test "devrait avoir coverages vide si pas de police" {
|
|
281
|
+
input = {vin: "2HGCM82633A004353"}
|
|
282
|
+
|
|
283
|
+
expect.to_be_empty ($response.coverages)
|
|
284
|
+
expect.to_be_null ($response.policy)
|
|
285
|
+
expect.to_equal ($response.claims_summary.total_claims) {
|
|
286
|
+
value = 0
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
expect.to_be_empty ($response.claims_summary.recent_claims)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
guid = "n20O4mlUR72ZWVYiGAE-QFk_ZO4"
|
|
293
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { LongFormVariable,ShortFormVariable } from "../lexer/variables.js";
|
|
2
|
+
import { BLACKLISTED_VARIABLE_NAMES } from "./generic/assignableVariableProperty.js";
|
|
3
|
+
|
|
4
|
+
const SHORT_FORM = ShortFormVariable.name;
|
|
5
|
+
const LONG_FORM = LongFormVariable.name;
|
|
6
|
+
|
|
7
|
+
const BUILTIN_NAMES = new Set(BLACKLISTED_VARIABLE_NAMES.map((n) => `$${n}`));
|
|
8
|
+
// Also skip $$ (temporary variable)
|
|
9
|
+
BUILTIN_NAMES.add("$$");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Scan the token stream for variable usages (ShortFormVariable and LongFormVariable tokens).
|
|
13
|
+
* Excludes tokens at declaration offsets and built-in variable names.
|
|
14
|
+
* @param {import('chevrotain').IToken[]} tokens - The token stream
|
|
15
|
+
* @param {Array<{name: string, startOffset: number | null}>} declarations - Known declaration sites
|
|
16
|
+
* @returns {Array<{name: string, startOffset: number, endOffset: number}>}
|
|
17
|
+
*/
|
|
18
|
+
export function scanVariableUsages(tokens, declarations) {
|
|
19
|
+
// Build set of declaration offsets to skip
|
|
20
|
+
const declOffsets = new Set();
|
|
21
|
+
for (const decl of declarations) {
|
|
22
|
+
if (decl.startOffset != null) {
|
|
23
|
+
declOffsets.add(decl.startOffset);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const usages = [];
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
30
|
+
const token = tokens[i];
|
|
31
|
+
const typeName = token.tokenType.name;
|
|
32
|
+
|
|
33
|
+
if (typeName === SHORT_FORM) {
|
|
34
|
+
// Skip if this is a declaration site
|
|
35
|
+
if (declOffsets.has(token.startOffset)) continue;
|
|
36
|
+
// Skip built-in variable names
|
|
37
|
+
if (BUILTIN_NAMES.has(token.image)) continue;
|
|
38
|
+
|
|
39
|
+
usages.push({
|
|
40
|
+
name: token.image,
|
|
41
|
+
startOffset: token.startOffset,
|
|
42
|
+
endOffset: token.endOffset,
|
|
43
|
+
});
|
|
44
|
+
} else if (typeName === LONG_FORM) {
|
|
45
|
+
// $var.foo → resolve to $foo
|
|
46
|
+
// Next tokens should be Dot + Identifier
|
|
47
|
+
if (i + 2 < tokens.length) {
|
|
48
|
+
const identToken = tokens[i + 2];
|
|
49
|
+
const name = `$${identToken.image}`;
|
|
50
|
+
|
|
51
|
+
// Skip if this is a declaration site (check the identifier token)
|
|
52
|
+
if (declOffsets.has(identToken.startOffset)) continue;
|
|
53
|
+
|
|
54
|
+
usages.push({
|
|
55
|
+
name,
|
|
56
|
+
startOffset: token.startOffset,
|
|
57
|
+
endOffset: identToken.endOffset,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return usages;
|
|
64
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { scanVariableUsages } from "./variableScanner.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate variable declarations vs usages.
|
|
5
|
+
* Returns undefined-variable warnings and unused-variable hints.
|
|
6
|
+
* @param {Object} symbolTable - The parser's __symbolTable
|
|
7
|
+
* @param {import('chevrotain').IToken[]} tokens - The token stream from parser.input
|
|
8
|
+
* @returns {{ warnings: Array<{message: string, startOffset: number, endOffset: number}>, hints: Array<{message: string, startOffset: number, endOffset: number}> }}
|
|
9
|
+
*/
|
|
10
|
+
export function validateVariables(symbolTable, tokens) {
|
|
11
|
+
const warnings = [];
|
|
12
|
+
const hints = [];
|
|
13
|
+
|
|
14
|
+
const declarations = symbolTable.varDeclarations || [];
|
|
15
|
+
const declaredNames = new Set(declarations.map((d) => d.name));
|
|
16
|
+
|
|
17
|
+
const usages = scanVariableUsages(tokens, declarations);
|
|
18
|
+
const usedNames = new Set(usages.map((u) => u.name));
|
|
19
|
+
|
|
20
|
+
// Undefined: used but not declared
|
|
21
|
+
for (const usage of usages) {
|
|
22
|
+
if (!declaredNames.has(usage.name)) {
|
|
23
|
+
warnings.push({
|
|
24
|
+
message: `Unknown variable '${usage.name}'`,
|
|
25
|
+
startOffset: usage.startOffset,
|
|
26
|
+
endOffset: usage.endOffset,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Unused: declared but never used
|
|
32
|
+
for (const decl of declarations) {
|
|
33
|
+
if (decl.startOffset == null) continue;
|
|
34
|
+
if (!usedNames.has(decl.name)) {
|
|
35
|
+
hints.push({
|
|
36
|
+
message: `Variable '${decl.name}' is declared but never used`,
|
|
37
|
+
startOffset: decl.startOffset,
|
|
38
|
+
endOffset: decl.endOffset,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { warnings, hints };
|
|
44
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { before, describe, it } from "mocha";
|
|
4
|
+
import { lexDocument } from "../lexer/lexer.js";
|
|
5
|
+
import { xanoscriptParser } from "./parser.js";
|
|
6
|
+
import { scanVariableUsages } from "./variableScanner.js";
|
|
7
|
+
import { validateVariables } from "./variableValidator.js";
|
|
8
|
+
|
|
9
|
+
describe("variable tracking - coverage_check.xs", () => {
|
|
10
|
+
let varDeclarations;
|
|
11
|
+
let usages;
|
|
12
|
+
let result;
|
|
13
|
+
let text;
|
|
14
|
+
|
|
15
|
+
before(() => {
|
|
16
|
+
text = readFileSync(
|
|
17
|
+
new URL("./tests/variable_test/coverage_check.xs", import.meta.url),
|
|
18
|
+
"utf8",
|
|
19
|
+
);
|
|
20
|
+
const lexResult = lexDocument(text);
|
|
21
|
+
const parser = xanoscriptParser(text);
|
|
22
|
+
expect(parser.errors).to.be.empty;
|
|
23
|
+
varDeclarations = [...parser.__symbolTable.varDeclarations];
|
|
24
|
+
usages = scanVariableUsages(lexResult.tokens, varDeclarations);
|
|
25
|
+
result = validateVariables(parser.__symbolTable, lexResult.tokens);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function lineOf(offset) {
|
|
29
|
+
return text.substring(0, offset).split("\n").length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function declLines(name) {
|
|
33
|
+
return varDeclarations
|
|
34
|
+
.filter((d) => d.name === name)
|
|
35
|
+
.map((d) => lineOf(d.startOffset));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function usageLines(name) {
|
|
39
|
+
return usages.filter((u) => u.name === name).map((u) => lineOf(u.startOffset));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// --- Declarations: each variable should be declared exactly once ---
|
|
43
|
+
|
|
44
|
+
it("$vehicle declared once at line 26 via 'as $vehicle'", () => {
|
|
45
|
+
expect(declLines("$vehicle")).to.deep.equal([26]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("$unused_variable declared once at line 35 via 'var'", () => {
|
|
49
|
+
expect(declLines("$unused_variable")).to.deep.equal([35]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("$vehicle_response declared once at line 40 via 'var'", () => {
|
|
53
|
+
expect(declLines("$vehicle_response")).to.deep.equal([40]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("$policy declared once at line 63 via 'as $policy'", () => {
|
|
57
|
+
expect(declLines("$policy")).to.deep.equal([63]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("$policy_response declared once at line 66 via 'var' (NOT again at line 87 via var.update)", () => {
|
|
61
|
+
expect(declLines("$policy_response")).to.deep.equal([66]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("$coverages_response declared once at line 70 via 'var'", () => {
|
|
65
|
+
expect(declLines("$coverages_response")).to.deep.equal([70]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("$claims_summary declared once at line 74 via 'var' (NOT again at line 188 via var.update)", () => {
|
|
69
|
+
expect(declLines("$claims_summary")).to.deep.equal([74]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("$policy_coverages declared once at line 105 via 'as'", () => {
|
|
73
|
+
expect(declLines("$policy_coverages")).to.deep.equal([105]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("$pc declared once at line 109 via 'each as $pc'", () => {
|
|
77
|
+
expect(declLines("$pc")).to.deep.equal([109]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("$ctype declared once at line 113 via 'as $ctype'", () => {
|
|
81
|
+
expect(declLines("$ctype")).to.deep.equal([113]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("$coverage_item declared once at line 116 via 'var'", () => {
|
|
85
|
+
expect(declLines("$coverage_item")).to.deep.equal([116]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("$all_claims declared once at line 142 via 'as'", () => {
|
|
89
|
+
expect(declLines("$all_claims")).to.deep.equal([142]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("$total_claims_count declared once at line 145 via 'var'", () => {
|
|
93
|
+
expect(declLines("$total_claims_count")).to.deep.equal([145]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("$two_years_ago declared once at line 150 via 'var'", () => {
|
|
97
|
+
expect(declLines("$two_years_ago")).to.deep.equal([150]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("$recent_claims_list declared once at line 155 via 'var'", () => {
|
|
101
|
+
expect(declLines("$recent_claims_list")).to.deep.equal([155]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("$claim declared once at line 160 via 'each as $claim'", () => {
|
|
105
|
+
expect(declLines("$claim")).to.deep.equal([160]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("$claim_date_ts declared once at line 162 via 'var'", () => {
|
|
109
|
+
expect(declLines("$claim_date_ts")).to.deep.equal([162]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("$recent_claim_item declared once at line 169 via 'var'", () => {
|
|
113
|
+
expect(declLines("$recent_claim_item")).to.deep.equal([169]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("$queried_by declared once at line 202 via 'var' (NOT again at line 217 via var.update)", () => {
|
|
117
|
+
expect(declLines("$queried_by")).to.deep.equal([202]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("$shop declared once at line 212 via 'as'", () => {
|
|
121
|
+
expect(declLines("$shop")).to.deep.equal([212]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("$result declared once at line 230 via 'var' (NOT again at line 242 via var.update)", () => {
|
|
125
|
+
expect(declLines("$result")).to.deep.equal([230]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// --- Usages: var.update targets count as usages ---
|
|
129
|
+
|
|
130
|
+
it("$vehicle used at lines 29, 42-46, 57", () => {
|
|
131
|
+
expect(usageLines("$vehicle")).to.deep.equal([29, 42, 43, 44, 45, 46, 57]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("$policy_response used at line 87 (var.update) and 233", () => {
|
|
135
|
+
expect(usageLines("$policy_response")).to.include(87);
|
|
136
|
+
expect(usageLines("$policy_response")).to.include(233);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("$claims_summary used at line 188 (var.update) and 235", () => {
|
|
140
|
+
expect(usageLines("$claims_summary")).to.include(188);
|
|
141
|
+
expect(usageLines("$claims_summary")).to.include(235);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("$queried_by used at line 217 (var.update), 241, 243", () => {
|
|
145
|
+
expect(usageLines("$queried_by")).to.include(217);
|
|
146
|
+
expect(usageLines("$queried_by")).to.include(241);
|
|
147
|
+
expect(usageLines("$queried_by")).to.include(243);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("$result used at line 242 (var.update), 243, 251", () => {
|
|
151
|
+
expect(usageLines("$result")).to.include(242);
|
|
152
|
+
expect(usageLines("$result")).to.include(243);
|
|
153
|
+
expect(usageLines("$result")).to.include(251);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("$coverages_response used at lines 127 and 234", () => {
|
|
157
|
+
expect(usageLines("$coverages_response")).to.include(127);
|
|
158
|
+
expect(usageLines("$coverages_response")).to.include(234);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("$pc used at lines 112, 120-122", () => {
|
|
162
|
+
expect(usageLines("$pc")).to.deep.equal([112, 120, 121, 122]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("$claim used at lines 163, 171-175", () => {
|
|
166
|
+
expect(usageLines("$claim")).to.deep.equal([163, 171, 172, 173, 174, 175]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// --- Validation results ---
|
|
170
|
+
|
|
171
|
+
it("should produce zero undefined-variable warnings", () => {
|
|
172
|
+
expect(result.warnings).to.be.empty;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should flag only $unused_variable as unused", () => {
|
|
176
|
+
expect(result.hints).to.have.lengthOf(1);
|
|
177
|
+
expect(result.hints[0].message).to.include("$unused_variable");
|
|
178
|
+
});
|
|
179
|
+
});
|