@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,CAs3OpC;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"}
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
- // Skip if no Parameters property
4661
- if (!parametersNode)
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
- // Additional check: Validate against spec queries list
4855
- if (queryName && componentSpec?.dataRequirements?.queries) {
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
- // Only validate if we're CERTAIN it's from RunQuery or RunView
6284
- if (isFromRunQuery || isFromRunView) {
6285
- // WHITELIST APPROACH: Check if the property is valid for the result type
6286
- const isValidQueryProp = validRunQueryResultProps.has(propName);
6287
- const isValidViewProp = validRunViewResultProps.has(propName);
6288
- // If it's specifically from RunQuery or RunView, be more specific
6289
- if (isFromRunQuery && !isValidQueryProp) {
6290
- // Property is not valid for RunQueryResult
6291
- const suggestion = incorrectToCorrectMap[propName];
6292
- if (suggestion) {
6293
- violations.push({
6294
- rule: 'runquery-result-invalid-property',
6295
- severity: 'critical',
6296
- line: path.node.loc?.start.line || 0,
6297
- column: path.node.loc?.start.column || 0,
6298
- message: `RunQuery results don't have a ".${propName}" property. Use ".${suggestion}" instead. Change "${objName}.${propName}" to "${objName}.${suggestion}"`,
6299
- code: `${objName}.${propName}`
6300
- });
6301
- }
6302
- else {
6303
- violations.push({
6304
- rule: 'runquery-result-invalid-property',
6305
- severity: 'critical',
6306
- line: path.node.loc?.start.line || 0,
6307
- column: path.node.loc?.start.column || 0,
6308
- message: `Invalid property "${propName}" on RunQuery result. Valid properties are: ${Array.from(validRunQueryResultProps).join(', ')}`,
6309
- code: `${objName}.${propName}`
6310
- });
6311
- }
6312
- }
6313
- else if (isFromRunView && !isValidViewProp) {
6314
- // Property is not valid for RunViewResult
6315
- const suggestion = incorrectToCorrectMap[propName];
6316
- if (suggestion) {
6317
- violations.push({
6318
- rule: 'runview-result-invalid-property',
6319
- severity: 'critical',
6320
- line: path.node.loc?.start.line || 0,
6321
- column: path.node.loc?.start.column || 0,
6322
- message: `RunView results don't have a ".${propName}" property. Use ".${suggestion}" instead. Change "${objName}.${propName}" to "${objName}.${suggestion}"`,
6323
- code: `${objName}.${propName}`
6324
- });
6325
- }
6326
- else {
6327
- violations.push({
6328
- rule: 'runview-result-invalid-property',
6329
- severity: 'critical',
6330
- line: path.node.loc?.start.line || 0,
6331
- column: path.node.loc?.start.column || 0,
6332
- message: `Invalid property "${propName}" on RunView result. Valid properties are: ${Array.from(validRunViewResultProps).join(', ')}`,
6333
- code: `${objName}.${propName}`
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