bulltrackers-module 1.0.732 → 1.0.733
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/functions/orchestrator/index.js +19 -17
- package/index.js +8 -29
- package/package.json +1 -1
- package/functions/computation-system/WorkflowOrchestrator.js +0 -213
- package/functions/computation-system/config/monitoring_config.js +0 -31
- package/functions/computation-system/config/validation_overrides.js +0 -10
- package/functions/computation-system/context/ContextFactory.js +0 -143
- package/functions/computation-system/context/ManifestBuilder.js +0 -379
- package/functions/computation-system/data/AvailabilityChecker.js +0 -236
- package/functions/computation-system/data/CachedDataLoader.js +0 -325
- package/functions/computation-system/data/DependencyFetcher.js +0 -455
- package/functions/computation-system/executors/MetaExecutor.js +0 -279
- package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
- package/functions/computation-system/executors/StandardExecutor.js +0 -465
- package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
- package/functions/computation-system/helpers/computation_worker.js +0 -375
- package/functions/computation-system/helpers/monitor.js +0 -64
- package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
- package/functions/computation-system/layers/extractors.js +0 -1097
- package/functions/computation-system/layers/index.js +0 -40
- package/functions/computation-system/layers/mathematics.js +0 -522
- package/functions/computation-system/layers/profiling.js +0 -537
- package/functions/computation-system/layers/validators.js +0 -170
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
- package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
- package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
- package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
- package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
- package/functions/computation-system/logger/logger.js +0 -297
- package/functions/computation-system/persistence/ContractValidator.js +0 -81
- package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
- package/functions/computation-system/persistence/ResultCommitter.js +0 -283
- package/functions/computation-system/persistence/ResultsValidator.js +0 -130
- package/functions/computation-system/persistence/RunRecorder.js +0 -142
- package/functions/computation-system/persistence/StatusRepository.js +0 -52
- package/functions/computation-system/reporter_epoch.js +0 -6
- package/functions/computation-system/scripts/UpdateContracts.js +0 -128
- package/functions/computation-system/services/SnapshotService.js +0 -148
- package/functions/computation-system/simulation/Fabricator.js +0 -285
- package/functions/computation-system/simulation/SeededRandom.js +0 -41
- package/functions/computation-system/simulation/SimRunner.js +0 -51
- package/functions/computation-system/system_epoch.js +0 -2
- package/functions/computation-system/tools/BuildReporter.js +0 -531
- package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
- package/functions/computation-system/tools/DeploymentValidator.js +0 -536
- package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
- package/functions/computation-system/topology/HashManager.js +0 -55
- package/functions/computation-system/topology/ManifestLoader.js +0 -47
- package/functions/computation-system/utils/data_loader.js +0 -675
- package/functions/computation-system/utils/schema_capture.js +0 -121
- package/functions/computation-system/utils/utils.js +0 -188
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Comprehensive Computation Validator
|
|
3
|
-
* Validates computation structure, metadata, and code compliance during build
|
|
4
|
-
* UPDATED: Added Static Dependency Verification to prove context.computed access.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const acorn = require('acorn');
|
|
8
|
-
const walk = require('acorn-walk');
|
|
9
|
-
|
|
10
|
-
// Import allowed layer exports for validation
|
|
11
|
-
const mathLayer = require('../layers/mathematics');
|
|
12
|
-
const extractorsLayer = require('../layers/extractors');
|
|
13
|
-
const profilingLayer = require('../layers/profiling');
|
|
14
|
-
const validatorsLayer = require('../layers/validators');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* VALIDATION RULESET CONFIGURATION
|
|
18
|
-
*/
|
|
19
|
-
const VALIDATION_RULES = {
|
|
20
|
-
// Allowed metadata fields
|
|
21
|
-
ALLOWED_METADATA_FIELDS: new Set([
|
|
22
|
-
'name', 'type', 'category', 'isPage', 'isHistorical', 'isTest',
|
|
23
|
-
'rootDataDependencies', 'rootDataSeries', 'dependencySeries',
|
|
24
|
-
'mandatoryRoots', 'canHaveMissingRoots', 'canHaveMissingSeries',
|
|
25
|
-
'userType', 'schedule', 'ttlDays', 'isAlertComputation',
|
|
26
|
-
'requiresEarliestDataDate'
|
|
27
|
-
]),
|
|
28
|
-
|
|
29
|
-
// Valid computation types
|
|
30
|
-
VALID_TYPES: new Set(['standard', 'meta']),
|
|
31
|
-
|
|
32
|
-
// Valid user types
|
|
33
|
-
VALID_USER_TYPES: new Set([
|
|
34
|
-
'all', 'normal', 'speculator', 'popular_investor', 'signed_in_user',
|
|
35
|
-
'NORMAL', 'SPECULATOR', 'POPULAR_INVESTOR', 'SIGNED_IN_USER'
|
|
36
|
-
]),
|
|
37
|
-
|
|
38
|
-
// Valid root data dependencies
|
|
39
|
-
VALID_ROOT_DATA_DEPS: new Set([
|
|
40
|
-
'portfolio', 'history', 'social', 'insights', 'price',
|
|
41
|
-
'rankings', 'verification', 'ratings', 'pageViews',
|
|
42
|
-
'watchlist', 'alerts', 'piMasterList'
|
|
43
|
-
]),
|
|
44
|
-
|
|
45
|
-
// Valid schedule types
|
|
46
|
-
VALID_SCHEDULE_TYPES: new Set(['DAILY', 'WEEKLY', 'MONTHLY']),
|
|
47
|
-
|
|
48
|
-
// Context structure for 'standard' type
|
|
49
|
-
STANDARD_CONTEXT_STRUCTURE: new Set([
|
|
50
|
-
'user', 'date', 'insights', 'social', 'mappings', 'math',
|
|
51
|
-
'computed', 'previousComputed', 'meta', 'config', 'deps',
|
|
52
|
-
'globalData', 'system'
|
|
53
|
-
]),
|
|
54
|
-
|
|
55
|
-
// Context structure for 'meta' type
|
|
56
|
-
META_CONTEXT_STRUCTURE: new Set([
|
|
57
|
-
'date', 'insights', 'social', 'prices', 'mappings', 'math',
|
|
58
|
-
'computed', 'previousComputed', 'meta', 'config', 'deps',
|
|
59
|
-
'globalData'
|
|
60
|
-
]),
|
|
61
|
-
|
|
62
|
-
// Allowed layer functions (built dynamically)
|
|
63
|
-
ALLOWED_LAYER_FUNCTIONS: new Map(),
|
|
64
|
-
|
|
65
|
-
// Forbidden patterns
|
|
66
|
-
FORBIDDEN_PATTERNS: [
|
|
67
|
-
{ pattern: /localStorage/g, message: 'localStorage is not supported in Cloud Functions' },
|
|
68
|
-
{ pattern: /sessionStorage/g, message: 'sessionStorage is not supported in Cloud Functions' },
|
|
69
|
-
{ pattern: /window\./g, message: 'window object is not available in Node.js environment' },
|
|
70
|
-
{ pattern: /document\./g, message: 'document object is not available in Node.js environment' },
|
|
71
|
-
{ pattern: /require\(/g, message: 'Dynamic require() is forbidden. Use dependency injection.' },
|
|
72
|
-
{ pattern: /eval\(/g, message: 'eval() is forbidden for security reasons' },
|
|
73
|
-
{ pattern: /Function\(/g, message: 'Function constructor is forbidden for security reasons' }
|
|
74
|
-
],
|
|
75
|
-
|
|
76
|
-
// Warning patterns (non-blocking)
|
|
77
|
-
WARNING_PATTERNS: [
|
|
78
|
-
{ pattern: /console\.log/g, message: 'Use context.deps.logger instead of console.log' },
|
|
79
|
-
{ pattern: /Math\.random/g, message: 'Math.random() is non-deterministic. Consider using SeededRandom for reproducibility.' },
|
|
80
|
-
{ pattern: /new Date\(\)/g, message: 'new Date() without arguments may cause timezone issues. Use context.date.today.' }
|
|
81
|
-
]
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Build allowed layer functions map
|
|
85
|
-
function buildLayerFunctionsMap() {
|
|
86
|
-
const map = new Map();
|
|
87
|
-
|
|
88
|
-
const layers = {
|
|
89
|
-
math: mathLayer,
|
|
90
|
-
extractors: extractorsLayer,
|
|
91
|
-
profiling: profilingLayer,
|
|
92
|
-
validators: validatorsLayer
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
for (const [layerName, layerExports] of Object.entries(layers)) {
|
|
96
|
-
for (const exportName of Object.keys(layerExports)) {
|
|
97
|
-
const fullPath = `${layerName}.${exportName}`;
|
|
98
|
-
map.set(fullPath, { layer: layerName, export: exportName });
|
|
99
|
-
|
|
100
|
-
// Also allow direct access (e.g., MathPrimitives)
|
|
101
|
-
map.set(exportName, { layer: layerName, export: exportName });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return map;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
VALIDATION_RULES.ALLOWED_LAYER_FUNCTIONS = buildLayerFunctionsMap();
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* MAIN VALIDATOR CLASS
|
|
112
|
-
*/
|
|
113
|
-
class ComputationValidator {
|
|
114
|
-
constructor() {
|
|
115
|
-
this.errors = [];
|
|
116
|
-
this.warnings = [];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Validates a single calculation class
|
|
121
|
-
*/
|
|
122
|
-
validate(CalcClass) {
|
|
123
|
-
const name = CalcClass.name || 'Unknown';
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
// 1. Structural Validation
|
|
127
|
-
this.validateStructure(CalcClass, name);
|
|
128
|
-
|
|
129
|
-
// 2. Metadata Validation
|
|
130
|
-
const metadata = CalcClass.getMetadata();
|
|
131
|
-
this.validateMetadata(metadata, name);
|
|
132
|
-
|
|
133
|
-
// 3. Dependencies Validation
|
|
134
|
-
const dependencies = CalcClass.getDependencies();
|
|
135
|
-
this.validateDependencies(dependencies, name);
|
|
136
|
-
|
|
137
|
-
// 4. Code Analysis (AST)
|
|
138
|
-
// UPDATED: Passing dependencies to enable context.computed verification
|
|
139
|
-
this.validateCode(CalcClass, name, metadata, dependencies);
|
|
140
|
-
|
|
141
|
-
// 5. Schema Validation (if present)
|
|
142
|
-
if (typeof CalcClass.getSchema === 'function') {
|
|
143
|
-
this.validateSchema(CalcClass.getSchema(), name);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
} catch (e) {
|
|
147
|
-
this.errors.push(`[${name}] Validation crashed: ${e.message}`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* 1. Structural Validation
|
|
153
|
-
*/
|
|
154
|
-
validateStructure(CalcClass, name) {
|
|
155
|
-
const proto = CalcClass.prototype;
|
|
156
|
-
|
|
157
|
-
// Required static methods
|
|
158
|
-
if (typeof CalcClass.getMetadata !== 'function') {
|
|
159
|
-
this.errors.push(`[${name}] Missing static getMetadata() method`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (typeof CalcClass.getDependencies !== 'function') {
|
|
163
|
-
this.errors.push(`[${name}] Missing static getDependencies() method`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Required instance methods
|
|
167
|
-
if (typeof proto.process !== 'function') {
|
|
168
|
-
this.errors.push(`[${name}] Missing process() instance method`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (typeof proto.getResult !== 'function') {
|
|
172
|
-
this.errors.push(`[${name}] Missing getResult() instance method`);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* 2. Metadata Validation
|
|
178
|
-
*/
|
|
179
|
-
validateMetadata(metadata, name) {
|
|
180
|
-
if (!metadata || typeof metadata !== 'object') {
|
|
181
|
-
this.errors.push(`[${name}] getMetadata() must return an object`);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Check for unknown fields
|
|
186
|
-
for (const field of Object.keys(metadata)) {
|
|
187
|
-
if (!VALIDATION_RULES.ALLOWED_METADATA_FIELDS.has(field)) {
|
|
188
|
-
this.warnings.push(`[${name}] Unknown metadata field: "${field}"`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Validate type
|
|
193
|
-
if (!metadata.type) {
|
|
194
|
-
this.errors.push(`[${name}] Missing required metadata field: "type"`);
|
|
195
|
-
} else if (!VALIDATION_RULES.VALID_TYPES.has(metadata.type)) {
|
|
196
|
-
this.errors.push(`[${name}] Invalid type "${metadata.type}". Must be "standard" or "meta"`);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Validate category
|
|
200
|
-
if (!metadata.category) {
|
|
201
|
-
this.warnings.push(`[${name}] Missing metadata field: "category"`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Validate userType (for standard computations)
|
|
205
|
-
if (metadata.type === 'standard') {
|
|
206
|
-
if (!metadata.userType) {
|
|
207
|
-
this.warnings.push(`[${name}] Standard computation missing userType. Defaulting to "all"`);
|
|
208
|
-
} else if (!VALIDATION_RULES.VALID_USER_TYPES.has(metadata.userType)) {
|
|
209
|
-
this.errors.push(`[${name}] Invalid userType "${metadata.userType}"`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Validate rootDataDependencies
|
|
214
|
-
if (metadata.rootDataDependencies) {
|
|
215
|
-
if (!Array.isArray(metadata.rootDataDependencies)) {
|
|
216
|
-
this.errors.push(`[${name}] rootDataDependencies must be an array`);
|
|
217
|
-
} else {
|
|
218
|
-
for (const dep of metadata.rootDataDependencies) {
|
|
219
|
-
if (!VALIDATION_RULES.VALID_ROOT_DATA_DEPS.has(dep)) {
|
|
220
|
-
this.errors.push(`[${name}] Invalid root data dependency: "${dep}"`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Validate schedule
|
|
227
|
-
if (metadata.schedule) {
|
|
228
|
-
if (!metadata.schedule.type || !VALIDATION_RULES.VALID_SCHEDULE_TYPES.has(metadata.schedule.type)) {
|
|
229
|
-
this.errors.push(`[${name}] Invalid schedule type. Must be DAILY, WEEKLY, or MONTHLY`);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (metadata.schedule.type === 'WEEKLY' && !metadata.schedule.days) {
|
|
233
|
-
this.errors.push(`[${name}] Weekly schedule missing "days" array`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (metadata.schedule.type === 'MONTHLY' && !metadata.schedule.days) {
|
|
237
|
-
this.errors.push(`[${name}] Monthly schedule missing "days" array`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Validate TTL
|
|
242
|
-
if (metadata.ttlDays !== undefined) {
|
|
243
|
-
if (typeof metadata.ttlDays !== 'number' || metadata.ttlDays < 0) {
|
|
244
|
-
this.errors.push(`[${name}] ttlDays must be a positive number`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Validate boolean flags
|
|
249
|
-
const booleanFields = ['isPage', 'isHistorical', 'isTest', 'canHaveMissingRoots', 'canHaveMissingSeries', 'isAlertComputation'];
|
|
250
|
-
for (const field of booleanFields) {
|
|
251
|
-
if (metadata[field] !== undefined && typeof metadata[field] !== 'boolean') {
|
|
252
|
-
this.errors.push(`[${name}] ${field} must be a boolean`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Validate series configurations
|
|
257
|
-
if (metadata.rootDataSeries) {
|
|
258
|
-
this.validateSeriesConfig(metadata.rootDataSeries, name, 'rootDataSeries');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (metadata.dependencySeries) {
|
|
262
|
-
this.validateSeriesConfig(metadata.dependencySeries, name, 'dependencySeries');
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
validateSeriesConfig(seriesConfig, name, fieldName) {
|
|
267
|
-
if (typeof seriesConfig !== 'object') {
|
|
268
|
-
this.errors.push(`[${name}] ${fieldName} must be an object`);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
for (const [key, config] of Object.entries(seriesConfig)) {
|
|
273
|
-
if (typeof config === 'number') {
|
|
274
|
-
if (config < 1 || config > 365) {
|
|
275
|
-
this.warnings.push(`[${name}] ${fieldName}.${key} lookback of ${config} days seems unusual`);
|
|
276
|
-
}
|
|
277
|
-
} else if (typeof config === 'object') {
|
|
278
|
-
if (!config.lookback || typeof config.lookback !== 'number') {
|
|
279
|
-
this.errors.push(`[${name}] ${fieldName}.${key} missing valid lookback property`);
|
|
280
|
-
}
|
|
281
|
-
} else {
|
|
282
|
-
this.errors.push(`[${name}] ${fieldName}.${key} must be a number or object with lookback`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* 3. Dependencies Validation
|
|
289
|
-
*/
|
|
290
|
-
validateDependencies(dependencies, name) {
|
|
291
|
-
if (!Array.isArray(dependencies)) {
|
|
292
|
-
this.errors.push(`[${name}] getDependencies() must return an array`);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Check for self-dependency
|
|
297
|
-
if (dependencies.includes(name)) {
|
|
298
|
-
this.errors.push(`[${name}] Computation cannot depend on itself`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Warn about empty dependencies for complex calculations
|
|
302
|
-
if (dependencies.length === 0) {
|
|
303
|
-
this.warnings.push(`[${name}] No dependencies declared. Is this intentional?`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* 4. Code Analysis (AST-based)
|
|
309
|
-
*/
|
|
310
|
-
validateCode(CalcClass, name, metadata, dependencies) {
|
|
311
|
-
const processSource = CalcClass.prototype.process.toString();
|
|
312
|
-
|
|
313
|
-
// Parse AST
|
|
314
|
-
let ast;
|
|
315
|
-
try {
|
|
316
|
-
const wrappedSource = `class __Guard { ${processSource} }`;
|
|
317
|
-
ast = acorn.parse(wrappedSource, { ecmaVersion: 2022, sourceType: 'script' });
|
|
318
|
-
} catch (parseError) {
|
|
319
|
-
this.errors.push(`[${name}] Failed to parse process() code: ${parseError.message}`);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Check forbidden patterns
|
|
324
|
-
for (const { pattern, message } of VALIDATION_RULES.FORBIDDEN_PATTERNS) {
|
|
325
|
-
if (pattern.test(processSource)) {
|
|
326
|
-
this.errors.push(`[${name}] ${message}`);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Check warning patterns
|
|
331
|
-
for (const { pattern, message } of VALIDATION_RULES.WARNING_PATTERNS) {
|
|
332
|
-
if (pattern.test(processSource)) {
|
|
333
|
-
this.warnings.push(`[${name}] ${message}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// AST Analysis
|
|
338
|
-
this.analyzeContextUsage(ast, name, metadata);
|
|
339
|
-
this.analyzeLayerUsage(ast, name);
|
|
340
|
-
this.analyzeResultAssignment(ast, name, metadata);
|
|
341
|
-
// UPDATED: Analyze Dependency Validity
|
|
342
|
-
this.analyzeDependencyUsage(ast, name, dependencies);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
analyzeContextUsage(ast, name, metadata) {
|
|
346
|
-
const allowedContext = metadata.type === 'standard'
|
|
347
|
-
? VALIDATION_RULES.STANDARD_CONTEXT_STRUCTURE
|
|
348
|
-
: VALIDATION_RULES.META_CONTEXT_STRUCTURE;
|
|
349
|
-
|
|
350
|
-
const usedContextProps = new Set();
|
|
351
|
-
|
|
352
|
-
walk.simple(ast, {
|
|
353
|
-
MemberExpression(node) {
|
|
354
|
-
// Check for context.PROPERTY patterns
|
|
355
|
-
if (node.object.type === 'Identifier' && node.object.name === 'context') {
|
|
356
|
-
if (node.property.type === 'Identifier') {
|
|
357
|
-
usedContextProps.add(node.property.name);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// Validate used context properties
|
|
364
|
-
for (const prop of usedContextProps) {
|
|
365
|
-
if (!allowedContext.has(prop)) {
|
|
366
|
-
this.warnings.push(`[${name}] Unknown context property: context.${prop}`);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
analyzeLayerUsage(ast, name) {
|
|
372
|
-
walk.simple(ast, {
|
|
373
|
-
MemberExpression(node) {
|
|
374
|
-
// Check for context.math.FUNCTION patterns
|
|
375
|
-
if (node.object.type === 'MemberExpression' &&
|
|
376
|
-
node.object.object.type === 'Identifier' &&
|
|
377
|
-
node.object.object.name === 'context' &&
|
|
378
|
-
node.object.property.name === 'math') {
|
|
379
|
-
|
|
380
|
-
if (node.property.type === 'Identifier') {
|
|
381
|
-
const funcName = node.property.name;
|
|
382
|
-
|
|
383
|
-
// Check if it's a known layer export
|
|
384
|
-
if (!VALIDATION_RULES.ALLOWED_LAYER_FUNCTIONS.has(funcName)) {
|
|
385
|
-
this.warnings.push(`[${name}] Unknown math layer function: ${funcName}`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* [NEW] Analyzes usage of context.computed to ensure variables exist
|
|
395
|
-
*/
|
|
396
|
-
analyzeDependencyUsage(ast, name, dependencies) {
|
|
397
|
-
const declaredSet = new Set(dependencies);
|
|
398
|
-
const self = this;
|
|
399
|
-
|
|
400
|
-
walk.simple(ast, {
|
|
401
|
-
MemberExpression(node) {
|
|
402
|
-
// Look for: context.computed['Key'] OR context.computed.Key
|
|
403
|
-
if (node.object.type === 'MemberExpression' &&
|
|
404
|
-
node.object.object.type === 'Identifier' &&
|
|
405
|
-
node.object.object.name === 'context' &&
|
|
406
|
-
node.object.property.name === 'computed') {
|
|
407
|
-
|
|
408
|
-
let accessedKey = null;
|
|
409
|
-
|
|
410
|
-
// Case 1: Dot notation (context.computed.SomeDep)
|
|
411
|
-
if (!node.computed && node.property.type === 'Identifier') {
|
|
412
|
-
accessedKey = node.property.name;
|
|
413
|
-
}
|
|
414
|
-
// Case 2: Bracket notation with string literal (context.computed['SomeDep'])
|
|
415
|
-
else if (node.computed && node.property.type === 'Literal') {
|
|
416
|
-
accessedKey = node.property.value;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (accessedKey) {
|
|
420
|
-
if (!declaredSet.has(accessedKey)) {
|
|
421
|
-
// Check for casing mismatches to give helpful errors
|
|
422
|
-
const caseInsensitiveMatch = dependencies.find(d => d.toLowerCase() === accessedKey.toLowerCase());
|
|
423
|
-
|
|
424
|
-
if (caseInsensitiveMatch) {
|
|
425
|
-
self.errors.push(`[${name}] Dependency Casing Mismatch: Accessed '${accessedKey}' but declared '${caseInsensitiveMatch}'. Keys are case-sensitive.`);
|
|
426
|
-
} else {
|
|
427
|
-
self.errors.push(`[${name}] Accessing undeclared dependency: '${accessedKey}'. Add it to getDependencies() or fix the typo.`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
} else if (node.computed) {
|
|
431
|
-
// Dynamic access (e.g. context.computed[someVar])
|
|
432
|
-
self.warnings.push(`[${name}] Dynamic access to context.computed detected. Cannot statically verify dependency existence.`);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
analyzeResultAssignment(ast, name, metadata) {
|
|
440
|
-
let assignsToResults = false;
|
|
441
|
-
let hasReturn = false;
|
|
442
|
-
|
|
443
|
-
walk.simple(ast, {
|
|
444
|
-
ReturnStatement(node) {
|
|
445
|
-
if (node.argument !== null) hasReturn = true;
|
|
446
|
-
},
|
|
447
|
-
AssignmentExpression(node) {
|
|
448
|
-
let target = node.left;
|
|
449
|
-
while (target && target.type === 'MemberExpression') {
|
|
450
|
-
if (target.object.type === 'ThisExpression' &&
|
|
451
|
-
target.property.name === 'results') {
|
|
452
|
-
assignsToResults = true;
|
|
453
|
-
break;
|
|
454
|
-
}
|
|
455
|
-
target = target.object;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
// Contract enforcement
|
|
461
|
-
if (metadata.type === 'meta' && !hasReturn) {
|
|
462
|
-
this.errors.push(`[${name}] Meta computation missing ReturnStatement in process()`);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (!assignsToResults && metadata.type === 'standard') {
|
|
466
|
-
this.warnings.push(`[${name}] Standard computation never assigns to this.results`);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* 5. Schema Validation
|
|
472
|
-
*/
|
|
473
|
-
validateSchema(schema, name) {
|
|
474
|
-
if (!schema || typeof schema !== 'object') {
|
|
475
|
-
this.warnings.push(`[${name}] getSchema() should return a valid JSON Schema object`);
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Basic schema structure check
|
|
480
|
-
if (!schema.type) {
|
|
481
|
-
this.warnings.push(`[${name}] Schema missing "type" field`);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Check for common schema patterns
|
|
485
|
-
if (schema.type === 'object') {
|
|
486
|
-
if (!schema.properties && !schema.patternProperties) {
|
|
487
|
-
this.warnings.push(`[${name}] Object schema has no properties or patternProperties`);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Get validation results
|
|
494
|
-
*/
|
|
495
|
-
getResults() {
|
|
496
|
-
return {
|
|
497
|
-
hasErrors: this.errors.length > 0,
|
|
498
|
-
errors: this.errors,
|
|
499
|
-
warnings: this.warnings
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Generate formatted report
|
|
505
|
-
*/
|
|
506
|
-
generateReport() {
|
|
507
|
-
const divider = '='.repeat(80);
|
|
508
|
-
const lines = [];
|
|
509
|
-
|
|
510
|
-
if (this.errors.length > 0) {
|
|
511
|
-
lines.push(`\n${divider}`);
|
|
512
|
-
lines.push(`VALIDATION ERRORS (${this.errors.length}):`);
|
|
513
|
-
this.errors.forEach((err, i) => {
|
|
514
|
-
lines.push(` ${i + 1}. ${err}`);
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (this.warnings.length > 0) {
|
|
519
|
-
lines.push(`\n${divider}`);
|
|
520
|
-
lines.push(`VALIDATION WARNINGS (${this.warnings.length}):`);
|
|
521
|
-
this.warnings.forEach((warn, i) => {
|
|
522
|
-
lines.push(` ${i + 1}. ${warn}`);
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
if (this.errors.length === 0 && this.warnings.length === 0) {
|
|
527
|
-
lines.push(`\n${divider}`);
|
|
528
|
-
lines.push('✓ ALL VALIDATIONS PASSED');
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
lines.push(`${divider}\n`);
|
|
532
|
-
return lines.join('\n');
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
module.exports = { ComputationValidator, VALIDATION_RULES };
|