@memberjunction/react-test-harness 2.118.0 → 2.120.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-linter.d.ts","sourceRoot":"","sources":["../../src/lib/component-linter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAiC,MAAM,6CAA6C,CAAC;AAG3G,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAG/D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,cAAc,CAAC;IACnF,UAAU,CAAC,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAiFD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,cAAc,CAAqB;IAGlD,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAQhC,OAAO,CAAC,MAAM,CAAC,cAAc;IAoB7B,OAAO,CAAC,MAAM,CAAC,4BAA4B;IA2C3C,OAAO,CAAC,MAAM,CAAC,uBAAuB,
|
|
1
|
+
{"version":3,"file":"component-linter.d.ts","sourceRoot":"","sources":["../../src/lib/component-linter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAiC,MAAM,6CAA6C,CAAC;AAG3G,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAG/D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,cAAc,CAAC;IACnF,UAAU,CAAC,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAiFD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,cAAc,CAAqB;IAGlD,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAQhC,OAAO,CAAC,MAAM,CAAC,cAAc;IAoB7B,OAAO,CAAC,MAAM,CAAC,4BAA4B;IA2C3C,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAk/OpC;WAEkB,uBAAuB,CACzC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;WAmC5B,aAAa,CAC/B,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,aAAa,EAC7B,eAAe,CAAC,EAAE,OAAO,EACzB,WAAW,CAAC,EAAE,QAAQ,EACtB,SAAS,CAAC,EAAE,OAAO,EACnB,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,UAAU,CAAC;IAqJtB,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAoXvC,OAAO,CAAC,MAAM,CAAC,eAAe;IA2B9B,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAyBpC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,0BAA0B;IA6hCzC,OAAO,CAAC,MAAM,CAAC,8BAA8B;IA2B7C;;OAEG;mBACkB,qBAAqB;IAuJ1C;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,0BAA0B;IAiEzC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IA8ClC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IA8GpC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAoElC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;CAkGzC"}
|
|
@@ -4657,9 +4657,57 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
|
|
|
4657
4657
|
}
|
|
4658
4658
|
}
|
|
4659
4659
|
}
|
|
4660
|
-
//
|
|
4661
|
-
|
|
4660
|
+
// IMPORTANT: Validate query name existence FIRST, before checking Parameters
|
|
4661
|
+
// This ensures we catch missing queries even when no Parameters are provided
|
|
4662
|
+
if (queryName && componentSpec?.dataRequirements?.queries) {
|
|
4663
|
+
const queryExists = componentSpec.dataRequirements.queries.some(q => q.name === queryName);
|
|
4664
|
+
if (!queryExists) {
|
|
4665
|
+
const availableQueries = componentSpec.dataRequirements.queries.map(q => q.name).join(', ');
|
|
4666
|
+
violations.push({
|
|
4667
|
+
rule: 'runquery-parameters-validation',
|
|
4668
|
+
severity: 'high',
|
|
4669
|
+
line: path.node.loc?.start.line || 0,
|
|
4670
|
+
column: path.node.loc?.start.column || 0,
|
|
4671
|
+
message: `Query '${queryName}' not found in component spec. Available queries: ${availableQueries || 'none'}`,
|
|
4672
|
+
code: `QueryName: '${componentSpec.dataRequirements.queries[0]?.name || 'QueryNameFromSpec'}'`
|
|
4673
|
+
});
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
// Check if query requires parameters but Parameters property is missing
|
|
4677
|
+
if (!parametersNode) {
|
|
4678
|
+
// Find the query spec to check if it has required parameters
|
|
4679
|
+
let specQuery;
|
|
4680
|
+
if (componentSpec?.dataRequirements?.queries && queryName) {
|
|
4681
|
+
specQuery = componentSpec.dataRequirements.queries.find(q => q.name === queryName);
|
|
4682
|
+
}
|
|
4683
|
+
if (specQuery?.parameters && specQuery.parameters.length > 0) {
|
|
4684
|
+
// Check if any parameters are required
|
|
4685
|
+
// Note: isRequired field is being added to ComponentQueryParameterValue type
|
|
4686
|
+
const requiredParams = specQuery.parameters.filter(p => {
|
|
4687
|
+
// Check for explicit isRequired flag (when available)
|
|
4688
|
+
const hasRequiredFlag = p.isRequired === true || p.isRequired === '1';
|
|
4689
|
+
// Or infer required if value is '@runtime' (runtime parameters should be provided)
|
|
4690
|
+
const isRuntimeParam = p.value === '@runtime';
|
|
4691
|
+
return hasRequiredFlag || isRuntimeParam;
|
|
4692
|
+
});
|
|
4693
|
+
if (requiredParams.length > 0) {
|
|
4694
|
+
const paramNames = requiredParams.map(p => p.name).join(', ');
|
|
4695
|
+
const exampleParams = requiredParams
|
|
4696
|
+
.map(p => ` ${p.name}: ${p.testValue ? `'${p.testValue}'` : "'value'"}`)
|
|
4697
|
+
.join(',\n');
|
|
4698
|
+
violations.push({
|
|
4699
|
+
rule: 'runquery-parameters-validation',
|
|
4700
|
+
severity: 'high',
|
|
4701
|
+
line: path.node.loc?.start.line || 0,
|
|
4702
|
+
column: path.node.loc?.start.column || 0,
|
|
4703
|
+
message: `Query '${queryName}' requires parameters but RunQuery call is missing 'Parameters' property. Required: ${paramNames}`,
|
|
4704
|
+
code: `Parameters: {\n${exampleParams}\n}`
|
|
4705
|
+
});
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
// Skip further parameter validation since there's no Parameters property
|
|
4662
4709
|
return;
|
|
4710
|
+
}
|
|
4663
4711
|
// Find the query in componentSpec if available
|
|
4664
4712
|
let specQuery;
|
|
4665
4713
|
if (componentSpec?.dataRequirements?.queries && queryName) {
|
|
@@ -4851,21 +4899,8 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
|
|
|
4851
4899
|
code: fixCode
|
|
4852
4900
|
});
|
|
4853
4901
|
}
|
|
4854
|
-
//
|
|
4855
|
-
|
|
4856
|
-
const queryExists = componentSpec.dataRequirements.queries.some(q => q.name === queryName);
|
|
4857
|
-
if (!queryExists) {
|
|
4858
|
-
const availableQueries = componentSpec.dataRequirements.queries.map(q => q.name).join(', ');
|
|
4859
|
-
violations.push({
|
|
4860
|
-
rule: 'runquery-parameters-validation',
|
|
4861
|
-
severity: 'high',
|
|
4862
|
-
line: path.node.loc?.start.line || 0,
|
|
4863
|
-
column: path.node.loc?.start.column || 0,
|
|
4864
|
-
message: `Query '${queryName}' not found in component spec. Available queries: ${availableQueries || 'none'}`,
|
|
4865
|
-
code: `QueryName: '${componentSpec.dataRequirements.queries[0]?.name || 'QueryNameFromSpec'}'`
|
|
4866
|
-
});
|
|
4867
|
-
}
|
|
4868
|
-
}
|
|
4902
|
+
// Note: Query name validation happens earlier (before Parameters check)
|
|
4903
|
+
// to ensure we catch missing queries even when no Parameters are provided
|
|
4869
4904
|
}
|
|
4870
4905
|
}
|
|
4871
4906
|
});
|
|
@@ -6269,6 +6304,39 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
|
|
|
6269
6304
|
'response': 'Results',
|
|
6270
6305
|
'Response': 'Results'
|
|
6271
6306
|
};
|
|
6307
|
+
// Helper function to validate property access and create violation
|
|
6308
|
+
const validatePropertyAccess = (objName, propName, isFromRunQuery, isFromRunView, line, column, code) => {
|
|
6309
|
+
if (!isFromRunQuery && !isFromRunView)
|
|
6310
|
+
return;
|
|
6311
|
+
const isValidQueryProp = validRunQueryResultProps.has(propName);
|
|
6312
|
+
const isValidViewProp = validRunViewResultProps.has(propName);
|
|
6313
|
+
if (isFromRunQuery && !isValidQueryProp) {
|
|
6314
|
+
const suggestion = incorrectToCorrectMap[propName];
|
|
6315
|
+
violations.push({
|
|
6316
|
+
rule: 'runquery-result-invalid-property',
|
|
6317
|
+
severity: 'critical',
|
|
6318
|
+
line,
|
|
6319
|
+
column,
|
|
6320
|
+
message: suggestion
|
|
6321
|
+
? `RunQuery results don't have a ".${propName}" property. Use ".${suggestion}" instead. Change "${objName}.${propName}" to "${objName}.${suggestion}"`
|
|
6322
|
+
: `Invalid property "${propName}" on RunQuery result. Valid properties are: ${Array.from(validRunQueryResultProps).join(', ')}`,
|
|
6323
|
+
code
|
|
6324
|
+
});
|
|
6325
|
+
}
|
|
6326
|
+
else if (isFromRunView && !isValidViewProp) {
|
|
6327
|
+
const suggestion = incorrectToCorrectMap[propName];
|
|
6328
|
+
violations.push({
|
|
6329
|
+
rule: 'runview-result-invalid-property',
|
|
6330
|
+
severity: 'critical',
|
|
6331
|
+
line,
|
|
6332
|
+
column,
|
|
6333
|
+
message: suggestion
|
|
6334
|
+
? `RunView results don't have a ".${propName}" property. Use ".${suggestion}" instead. Change "${objName}.${propName}" to "${objName}.${suggestion}"`
|
|
6335
|
+
: `Invalid property "${propName}" on RunView result. Valid properties are: ${Array.from(validRunViewResultProps).join(', ')}`,
|
|
6336
|
+
code
|
|
6337
|
+
});
|
|
6338
|
+
}
|
|
6339
|
+
};
|
|
6272
6340
|
(0, traverse_1.default)(ast, {
|
|
6273
6341
|
MemberExpression(path) {
|
|
6274
6342
|
// Check if this is accessing a property on a variable that looks like a query/view result
|
|
@@ -6280,75 +6348,87 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
|
|
|
6280
6348
|
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunQuery');
|
|
6281
6349
|
const isFromRunView = path.scope.hasBinding(objName) &&
|
|
6282
6350
|
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunView');
|
|
6283
|
-
//
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6351
|
+
// Use shared validation logic
|
|
6352
|
+
validatePropertyAccess(objName, propName, isFromRunQuery, isFromRunView, path.node.loc?.start.line || 0, path.node.loc?.start.column || 0, `${objName}.${propName}`);
|
|
6353
|
+
// Check for nested incorrect access like result.data.entities or result.Data.entities
|
|
6354
|
+
if ((isFromRunQuery || isFromRunView) &&
|
|
6355
|
+
t.isMemberExpression(path.parent) &&
|
|
6356
|
+
t.isIdentifier(path.parent.property) &&
|
|
6357
|
+
(propName === 'data' || propName === 'Data')) {
|
|
6358
|
+
const nestedProp = path.parent.property.name;
|
|
6359
|
+
violations.push({
|
|
6360
|
+
rule: 'runquery-runview-result-structure',
|
|
6361
|
+
severity: 'critical',
|
|
6362
|
+
line: path.parent.loc?.start.line || 0,
|
|
6363
|
+
column: path.parent.loc?.start.column || 0,
|
|
6364
|
+
message: `Incorrect nested property access "${objName}.${propName}.${nestedProp}". RunQuery/RunView results use ".Results" directly for the data array. Change to "${objName}.Results"`,
|
|
6365
|
+
code: `${objName}.${propName}.${nestedProp}`
|
|
6366
|
+
});
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
},
|
|
6370
|
+
// NEW: Handle optional chaining (result?.records, result?.Rows, etc.)
|
|
6371
|
+
OptionalMemberExpression(path) {
|
|
6372
|
+
if (t.isIdentifier(path.node.object) && t.isIdentifier(path.node.property)) {
|
|
6373
|
+
const objName = path.node.object.name;
|
|
6374
|
+
const propName = path.node.property.name;
|
|
6375
|
+
// Only check if we can definitively trace this to RunQuery or RunView
|
|
6376
|
+
const isFromRunQuery = path.scope.hasBinding(objName) &&
|
|
6377
|
+
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunQuery');
|
|
6378
|
+
const isFromRunView = path.scope.hasBinding(objName) &&
|
|
6379
|
+
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunView');
|
|
6380
|
+
// Use shared validation logic
|
|
6381
|
+
validatePropertyAccess(objName, propName, isFromRunQuery, isFromRunView, path.node.loc?.start.line || 0, path.node.loc?.start.column || 0, `${objName}?.${propName}`);
|
|
6382
|
+
}
|
|
6383
|
+
},
|
|
6384
|
+
// NEW: Detect weak fallback patterns like result?.records ?? result?.Rows ?? []
|
|
6385
|
+
LogicalExpression(path) {
|
|
6386
|
+
if (path.node.operator !== '??')
|
|
6387
|
+
return;
|
|
6388
|
+
// Collect all invalid property accesses in the chain
|
|
6389
|
+
const invalidAccesses = [];
|
|
6390
|
+
const checkNode = (node) => {
|
|
6391
|
+
if (t.isOptionalMemberExpression(node) &&
|
|
6392
|
+
t.isIdentifier(node.object) &&
|
|
6393
|
+
t.isIdentifier(node.property)) {
|
|
6394
|
+
const objName = node.object.name;
|
|
6395
|
+
const propName = node.property.name;
|
|
6396
|
+
// Check if this is from RunQuery/RunView
|
|
6397
|
+
const isFromRunQuery = path.scope.hasBinding(objName) &&
|
|
6398
|
+
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunQuery');
|
|
6399
|
+
const isFromRunView = path.scope.hasBinding(objName) &&
|
|
6400
|
+
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunView');
|
|
6401
|
+
if (isFromRunQuery || isFromRunView) {
|
|
6402
|
+
const isValidQueryProp = validRunQueryResultProps.has(propName);
|
|
6403
|
+
const isValidViewProp = validRunViewResultProps.has(propName);
|
|
6404
|
+
if ((isFromRunQuery && !isValidQueryProp) || (isFromRunView && !isValidViewProp)) {
|
|
6405
|
+
invalidAccesses.push({
|
|
6406
|
+
objName,
|
|
6407
|
+
propName,
|
|
6408
|
+
line: node.loc?.start.line || 0
|
|
6334
6409
|
});
|
|
6335
6410
|
}
|
|
6336
6411
|
}
|
|
6337
|
-
// Check for nested incorrect access like result.data.entities or result.Data.entities
|
|
6338
|
-
if (t.isMemberExpression(path.parent) &&
|
|
6339
|
-
t.isIdentifier(path.parent.property) &&
|
|
6340
|
-
(propName === 'data' || propName === 'Data')) {
|
|
6341
|
-
const nestedProp = path.parent.property.name;
|
|
6342
|
-
violations.push({
|
|
6343
|
-
rule: 'runquery-runview-result-structure',
|
|
6344
|
-
severity: 'critical',
|
|
6345
|
-
line: path.parent.loc?.start.line || 0,
|
|
6346
|
-
column: path.parent.loc?.start.column || 0,
|
|
6347
|
-
message: `Incorrect nested property access "${objName}.${propName}.${nestedProp}". RunQuery/RunView results use ".Results" directly for the data array. Change to "${objName}.Results"`,
|
|
6348
|
-
code: `${objName}.${propName}.${nestedProp}`
|
|
6349
|
-
});
|
|
6350
|
-
}
|
|
6351
6412
|
}
|
|
6413
|
+
else if (t.isLogicalExpression(node) && node.operator === '??') {
|
|
6414
|
+
// Recursively check chained ?? operators
|
|
6415
|
+
checkNode(node.left);
|
|
6416
|
+
checkNode(node.right);
|
|
6417
|
+
}
|
|
6418
|
+
};
|
|
6419
|
+
checkNode(path.node);
|
|
6420
|
+
// If we found multiple invalid accesses in a chain, report as weak fallback
|
|
6421
|
+
if (invalidAccesses.length >= 2) {
|
|
6422
|
+
const objName = invalidAccesses[0].objName;
|
|
6423
|
+
const props = invalidAccesses.map(a => a.propName).join(', ');
|
|
6424
|
+
violations.push({
|
|
6425
|
+
rule: 'runquery-runview-result-structure',
|
|
6426
|
+
severity: 'critical',
|
|
6427
|
+
line: path.node.loc?.start.line || 0,
|
|
6428
|
+
column: path.node.loc?.start.column || 0,
|
|
6429
|
+
message: `Weak fallback pattern detected: "${objName}?.${invalidAccesses[0].propName} ?? ${objName}?.${invalidAccesses[1].propName} ?? ..." uses multiple INVALID properties (${props}). This masks the real issue. Use "${objName}?.Results ?? []" instead. RunQuery/RunView results have a "Results" property (capital R), not "${props}".`,
|
|
6430
|
+
code: path.toString().substring(0, 100)
|
|
6431
|
+
});
|
|
6352
6432
|
}
|
|
6353
6433
|
},
|
|
6354
6434
|
// Check for destructuring patterns
|