@memberjunction/react-test-harness 2.120.0 → 2.121.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,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"}
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,CA45OpC;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"}
@@ -5414,228 +5414,6 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
5414
5414
  return violations;
5415
5415
  }
5416
5416
  },
5417
- {
5418
- name: 'validate-dependency-props',
5419
- appliesTo: 'all',
5420
- test: (ast, componentName, componentSpec) => {
5421
- const violations = [];
5422
- // Build a map of dependency components to their specs
5423
- const dependencySpecs = new Map();
5424
- // Process embedded dependencies
5425
- if (componentSpec?.dependencies && Array.isArray(componentSpec.dependencies)) {
5426
- for (const dep of componentSpec.dependencies) {
5427
- if (dep && dep.name) {
5428
- if (dep.location === 'registry') {
5429
- const match = core_entities_1.ComponentMetadataEngine.Instance.FindComponent(dep.name, dep.namespace, dep.registry);
5430
- if (!match) {
5431
- // the specified registry component was not found, we can't lint for it, but we should put a warning
5432
- console.warn('Dependency component not found in registry', dep);
5433
- }
5434
- else {
5435
- dependencySpecs.set(dep.name, match.spec);
5436
- }
5437
- }
5438
- else {
5439
- // Embedded dependencies have their spec inline
5440
- dependencySpecs.set(dep.name, dep);
5441
- }
5442
- }
5443
- else {
5444
- // we have an invalid dep in the spec, not a fatal error but we should log this
5445
- console.warn(`Invalid dependency in component spec`, dep);
5446
- }
5447
- }
5448
- }
5449
- // For registry dependencies, we'd need ComponentMetadataEngine
5450
- // But since this is a static lint check, we'll focus on embedded deps
5451
- // Registry components would need async loading which doesn't fit the current sync pattern
5452
- // Now traverse JSX to find component usage
5453
- (0, traverse_1.default)(ast, {
5454
- JSXElement(path) {
5455
- const openingElement = path.node.openingElement;
5456
- // Check if this is one of our dependency components
5457
- if (t.isJSXIdentifier(openingElement.name)) {
5458
- const componentName = openingElement.name.name;
5459
- const depSpec = dependencySpecs.get(componentName);
5460
- if (depSpec) {
5461
- // Collect props being passed
5462
- const passedProps = new Set();
5463
- const passedPropNodes = new Map();
5464
- for (const attr of openingElement.attributes) {
5465
- if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
5466
- const propName = attr.name.name;
5467
- passedProps.add(propName);
5468
- passedPropNodes.set(propName, attr);
5469
- }
5470
- }
5471
- // Check required custom props
5472
- if (depSpec.properties && Array.isArray(depSpec.properties)) {
5473
- const requiredProps = [];
5474
- const optionalProps = [];
5475
- for (const prop of depSpec.properties) {
5476
- if (prop && prop.name && typeof prop.name === 'string') {
5477
- if (prop.required === true) {
5478
- requiredProps.push(prop.name);
5479
- }
5480
- else {
5481
- optionalProps.push(prop.name);
5482
- }
5483
- }
5484
- }
5485
- // Check for missing required props
5486
- const missingRequired = requiredProps.filter(prop => {
5487
- // Special handling for 'children' prop
5488
- if (prop === 'children') {
5489
- // Check if JSX element has children nodes
5490
- const hasChildren = path.node.children && path.node.children.length > 0 &&
5491
- path.node.children.some(child => !t.isJSXText(child) || (t.isJSXText(child) && child.value.trim() !== ''));
5492
- return !passedProps.has(prop) && !hasChildren;
5493
- }
5494
- return !passedProps.has(prop);
5495
- });
5496
- // Separate children warnings from other critical props
5497
- const missingChildren = missingRequired.filter(prop => prop === 'children');
5498
- const missingOtherProps = missingRequired.filter(prop => prop !== 'children');
5499
- // Critical violation for non-children required props
5500
- if (missingOtherProps.length > 0) {
5501
- violations.push({
5502
- rule: 'validate-dependency-props',
5503
- severity: 'critical',
5504
- line: openingElement.loc?.start.line || 0,
5505
- column: openingElement.loc?.start.column || 0,
5506
- message: `Dependency component "${componentName}" is missing required props: ${missingOtherProps.join(', ')}. These props are marked as required in the component's specification.`,
5507
- code: `<${componentName} ... />`
5508
- });
5509
- }
5510
- // Medium severity warning for missing children when required
5511
- if (missingChildren.length > 0) {
5512
- violations.push({
5513
- rule: 'validate-dependency-props',
5514
- severity: 'medium',
5515
- line: openingElement.loc?.start.line || 0,
5516
- column: openingElement.loc?.start.column || 0,
5517
- message: `Component "${componentName}" expects children but none were provided. The 'children' prop is marked as required in the component's specification.`,
5518
- code: `<${componentName} ... />`
5519
- });
5520
- }
5521
- // Validate prop types for passed props
5522
- for (const [propName, attrNode] of passedPropNodes) {
5523
- const propSpec = depSpec.properties.find(p => p.name === propName);
5524
- if (propSpec && propSpec.type) {
5525
- const value = attrNode.value;
5526
- // Type validation based on prop spec type
5527
- if (propSpec.type === 'string') {
5528
- // Check if value could be a string
5529
- if (value && t.isJSXExpressionContainer(value)) {
5530
- const expr = value.expression;
5531
- // Check for obvious non-string types
5532
- if (t.isNumericLiteral(expr) || t.isBooleanLiteral(expr) ||
5533
- t.isArrayExpression(expr) || (t.isObjectExpression(expr) && !t.isTemplateLiteral(expr))) {
5534
- violations.push({
5535
- rule: 'validate-dependency-props',
5536
- severity: 'high',
5537
- line: attrNode.loc?.start.line || 0,
5538
- column: attrNode.loc?.start.column || 0,
5539
- message: `Prop "${propName}" on component "${componentName}" expects type "string" but received a different type.`,
5540
- code: `${propName}={...}`
5541
- });
5542
- }
5543
- }
5544
- }
5545
- else if (propSpec.type === 'number') {
5546
- // Check if value could be a number
5547
- if (value && t.isJSXExpressionContainer(value)) {
5548
- const expr = value.expression;
5549
- if (t.isStringLiteral(expr) || t.isBooleanLiteral(expr) ||
5550
- t.isArrayExpression(expr) || t.isObjectExpression(expr)) {
5551
- violations.push({
5552
- rule: 'validate-dependency-props',
5553
- severity: 'high',
5554
- line: attrNode.loc?.start.line || 0,
5555
- column: attrNode.loc?.start.column || 0,
5556
- message: `Prop "${propName}" on component "${componentName}" expects type "number" but received a different type.`,
5557
- code: `${propName}={...}`
5558
- });
5559
- }
5560
- }
5561
- }
5562
- else if (propSpec.type === 'boolean') {
5563
- // Check if value could be a boolean
5564
- if (value && t.isJSXExpressionContainer(value)) {
5565
- const expr = value.expression;
5566
- if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
5567
- t.isArrayExpression(expr) || t.isObjectExpression(expr)) {
5568
- violations.push({
5569
- rule: 'validate-dependency-props',
5570
- severity: 'high',
5571
- line: attrNode.loc?.start.line || 0,
5572
- column: attrNode.loc?.start.column || 0,
5573
- message: `Prop "${propName}" on component "${componentName}" expects type "boolean" but received a different type.`,
5574
- code: `${propName}={...}`
5575
- });
5576
- }
5577
- }
5578
- }
5579
- else if (propSpec.type === 'array') {
5580
- // Check if value could be an array
5581
- if (value && t.isJSXExpressionContainer(value)) {
5582
- const expr = value.expression;
5583
- if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
5584
- t.isBooleanLiteral(expr) || (t.isObjectExpression(expr) && !t.isArrayExpression(expr))) {
5585
- violations.push({
5586
- rule: 'validate-dependency-props',
5587
- severity: 'high',
5588
- line: attrNode.loc?.start.line || 0,
5589
- column: attrNode.loc?.start.column || 0,
5590
- message: `Prop "${propName}" on component "${componentName}" expects type "array" but received a different type.`,
5591
- code: `${propName}={...}`
5592
- });
5593
- }
5594
- }
5595
- }
5596
- else if (propSpec.type === 'object') {
5597
- // Check if value could be an object
5598
- if (value && t.isJSXExpressionContainer(value)) {
5599
- const expr = value.expression;
5600
- if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
5601
- t.isBooleanLiteral(expr) || t.isArrayExpression(expr)) {
5602
- violations.push({
5603
- rule: 'validate-dependency-props',
5604
- severity: 'high',
5605
- line: attrNode.loc?.start.line || 0,
5606
- column: attrNode.loc?.start.column || 0,
5607
- message: `Prop "${propName}" on component "${componentName}" expects type "object" but received a different type.`,
5608
- code: `${propName}={...}`
5609
- });
5610
- }
5611
- }
5612
- }
5613
- }
5614
- }
5615
- // Check for unknown props (props not in the spec)
5616
- const specPropNames = new Set(depSpec.properties.map(p => p.name).filter(Boolean));
5617
- const standardProps = new Set(['utilities', 'styles', 'components', 'callbacks', 'savedUserSettings', 'onSaveUserSettings']);
5618
- const reactSpecialProps = new Set(['children']);
5619
- for (const passedProp of passedProps) {
5620
- if (!specPropNames.has(passedProp) && !standardProps.has(passedProp) && !reactSpecialProps.has(passedProp)) {
5621
- violations.push({
5622
- rule: 'validate-dependency-props',
5623
- severity: 'medium',
5624
- line: passedPropNodes.get(passedProp)?.loc?.start.line || 0,
5625
- column: passedPropNodes.get(passedProp)?.loc?.start.column || 0,
5626
- message: `Prop "${passedProp}" is not defined in the specification for component "${componentName}". In addition to the standard MJ props, valid custom props: ${Array.from(specPropNames).join(', ') || 'none'}.`,
5627
- code: `${passedProp}={...}`
5628
- });
5629
- }
5630
- }
5631
- }
5632
- }
5633
- }
5634
- }
5635
- });
5636
- return violations;
5637
- }
5638
- },
5639
5417
  {
5640
5418
  name: 'unsafe-array-operations',
5641
5419
  appliesTo: 'all',
@@ -6733,20 +6511,38 @@ Correct pattern:
6733
6511
  if (!componentSpec?.dependencies || componentSpec.dependencies.length === 0) {
6734
6512
  return violations;
6735
6513
  }
6736
- // Build a map of dependency components and their expected props
6737
- const dependencyPropsMap = new Map();
6514
+ // Build a map of dependency components to their full specs
6515
+ const dependencySpecs = new Map();
6516
+ // Process all dependencies (embedded and registry)
6738
6517
  for (const dep of componentSpec.dependencies) {
6739
- const requiredProps = dep.properties
6740
- ?.filter(p => p.required)
6741
- ?.map(p => p.name) || [];
6742
- const allProps = dep.properties?.map(p => p.name) || [];
6743
- dependencyPropsMap.set(dep.name, {
6744
- required: requiredProps,
6745
- all: allProps,
6746
- location: dep.location || 'embedded'
6747
- });
6518
+ if (dep && dep.name) {
6519
+ if (dep.location === 'registry') {
6520
+ // Try to load from registry
6521
+ // check if registry is defined; if not, don't pass it to find component
6522
+ let match;
6523
+ if (dep.registry) {
6524
+ match = core_entities_1.ComponentMetadataEngine.Instance.FindComponent(dep.name, dep.namespace, dep.registry);
6525
+ }
6526
+ else {
6527
+ match = core_entities_1.ComponentMetadataEngine.Instance.FindComponent(dep.name, dep.namespace);
6528
+ }
6529
+ if (!match) {
6530
+ console.warn(`Dependency component not found in registry: ${dep.name} (${dep.namespace || 'no namespace'})`);
6531
+ }
6532
+ else {
6533
+ dependencySpecs.set(dep.name, match.spec);
6534
+ }
6535
+ }
6536
+ else {
6537
+ // Embedded dependencies have their spec inline
6538
+ dependencySpecs.set(dep.name, dep);
6539
+ }
6540
+ }
6541
+ else {
6542
+ console.warn(`Invalid dependency in component spec: ${dep?.name || 'unknown'}`);
6543
+ }
6748
6544
  }
6749
- // Helper function to find closest matching prop name
6545
+ // Helper function to find closest matching prop name using Levenshtein distance
6750
6546
  function findClosestMatch(target, candidates) {
6751
6547
  if (candidates.length === 0)
6752
6548
  return null;
@@ -6785,10 +6581,11 @@ Correct pattern:
6785
6581
  }
6786
6582
  // Standard props that are always valid (passed by the runtime)
6787
6583
  const standardProps = new Set([
6788
- 'styles', 'utilities', 'components', 'callbacks',
6584
+ 'utilities', 'styles', 'components', 'callbacks',
6789
6585
  'savedUserSettings', 'onSaveUserSettings'
6790
6586
  ]);
6791
- // Track JSX elements and their props
6587
+ const reactSpecialProps = new Set(['children', 'key', 'ref']);
6588
+ // Traverse JSX to find component usage
6792
6589
  (0, traverse_1.default)(ast, {
6793
6590
  JSXElement(path) {
6794
6591
  const openingElement = path.node.openingElement;
@@ -6798,69 +6595,204 @@ Correct pattern:
6798
6595
  elementName = openingElement.name.name;
6799
6596
  }
6800
6597
  else if (t.isJSXMemberExpression(openingElement.name)) {
6801
- // Handle cases like <MaterialUI.Button>
6802
- return; // Skip member expressions for now
6598
+ // Handle cases like <components.Button> - skip for now
6599
+ return;
6803
6600
  }
6804
- // Check if this is one of our dependencies
6805
- if (dependencyPropsMap.has(elementName)) {
6806
- const { required, all, location } = dependencyPropsMap.get(elementName);
6807
- // Get passed props
6808
- const passedProps = new Set();
6809
- const propLocations = new Map();
6810
- for (const attr of openingElement.attributes) {
6811
- if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
6812
- const propName = attr.name.name;
6813
- passedProps.add(propName);
6814
- propLocations.set(propName, {
6815
- line: attr.loc?.start.line || 0,
6816
- column: attr.loc?.start.column || 0
6817
- });
6601
+ // Check if this is one of our dependency components
6602
+ const depSpec = dependencySpecs.get(elementName);
6603
+ if (!depSpec)
6604
+ return;
6605
+ // Collect passed props
6606
+ const passedProps = new Set();
6607
+ const passedPropNodes = new Map();
6608
+ for (const attr of openingElement.attributes) {
6609
+ if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
6610
+ const propName = attr.name.name;
6611
+ passedProps.add(propName);
6612
+ passedPropNodes.set(propName, attr);
6613
+ }
6614
+ }
6615
+ // Build lists of valid props and events
6616
+ const specPropNames = depSpec.properties?.map(p => p.name).filter(Boolean) || [];
6617
+ const specEventNames = depSpec.events?.map(e => e.name).filter(Boolean) || [];
6618
+ const allValidProps = [...specPropNames, ...specEventNames];
6619
+ // Get required props
6620
+ const requiredProps = [];
6621
+ if (depSpec.properties && Array.isArray(depSpec.properties)) {
6622
+ for (const prop of depSpec.properties) {
6623
+ if (prop && prop.name && prop.required === true) {
6624
+ requiredProps.push(prop.name);
6625
+ }
6626
+ }
6627
+ }
6628
+ // ═══════════════════════════════════════════════════════════════
6629
+ // 1. CHECK MISSING REQUIRED PROPS
6630
+ // ═══════════════════════════════════════════════════════════════
6631
+ const missingRequired = requiredProps.filter(prop => {
6632
+ // Special handling for 'children' prop
6633
+ if (prop === 'children') {
6634
+ // Check if JSX element has children nodes
6635
+ const hasChildren = path.node.children && path.node.children.length > 0 &&
6636
+ path.node.children.some(child => !t.isJSXText(child) || (t.isJSXText(child) && child.value.trim() !== ''));
6637
+ return !passedProps.has(prop) && !hasChildren;
6638
+ }
6639
+ return !passedProps.has(prop) && !standardProps.has(prop);
6640
+ });
6641
+ // Separate children warnings from other critical props
6642
+ const missingChildren = missingRequired.filter(prop => prop === 'children');
6643
+ const missingOtherProps = missingRequired.filter(prop => prop !== 'children');
6644
+ // Critical violation for non-children required props
6645
+ if (missingOtherProps.length > 0) {
6646
+ violations.push({
6647
+ rule: 'dependency-prop-validation',
6648
+ severity: 'critical',
6649
+ line: openingElement.loc?.start.line || 0,
6650
+ column: openingElement.loc?.start.column || 0,
6651
+ message: `Dependency component "${elementName}" is missing required props: ${missingOtherProps.join(', ')}. These props are marked as required in the component's specification.`,
6652
+ code: `<${elementName} ... />`
6653
+ });
6654
+ }
6655
+ // Medium severity warning for missing children when required
6656
+ if (missingChildren.length > 0) {
6657
+ violations.push({
6658
+ rule: 'dependency-prop-validation',
6659
+ severity: 'medium',
6660
+ line: openingElement.loc?.start.line || 0,
6661
+ column: openingElement.loc?.start.column || 0,
6662
+ message: `Component "${elementName}" expects children but none were provided. The 'children' prop is marked as required in the component's specification.`,
6663
+ code: `<${elementName} ... />`
6664
+ });
6665
+ }
6666
+ // ═══════════════════════════════════════════════════════════════
6667
+ // 2. VALIDATE PROP TYPES
6668
+ // ═══════════════════════════════════════════════════════════════
6669
+ if (depSpec.properties && Array.isArray(depSpec.properties)) {
6670
+ for (const [propName, attrNode] of passedPropNodes) {
6671
+ const propSpec = depSpec.properties.find(p => p.name === propName);
6672
+ if (propSpec && propSpec.type) {
6673
+ const value = attrNode.value;
6674
+ // Type validation based on prop spec type
6675
+ if (propSpec.type === 'string') {
6676
+ // Check if value could be a string
6677
+ if (value && t.isJSXExpressionContainer(value)) {
6678
+ const expr = value.expression;
6679
+ // Check for obvious non-string types
6680
+ if (t.isNumericLiteral(expr) || t.isBooleanLiteral(expr) ||
6681
+ t.isArrayExpression(expr) || (t.isObjectExpression(expr) && !t.isTemplateLiteral(expr))) {
6682
+ violations.push({
6683
+ rule: 'dependency-prop-validation',
6684
+ severity: 'high',
6685
+ line: attrNode.loc?.start.line || 0,
6686
+ column: attrNode.loc?.start.column || 0,
6687
+ message: `Prop "${propName}" on component "${elementName}" expects type "string" but received a different type.`,
6688
+ code: `${propName}={...}`
6689
+ });
6690
+ }
6691
+ }
6692
+ }
6693
+ else if (propSpec.type === 'number') {
6694
+ // Check if value could be a number
6695
+ if (value && t.isJSXExpressionContainer(value)) {
6696
+ const expr = value.expression;
6697
+ if (t.isStringLiteral(expr) || t.isBooleanLiteral(expr) ||
6698
+ t.isArrayExpression(expr) || t.isObjectExpression(expr)) {
6699
+ violations.push({
6700
+ rule: 'dependency-prop-validation',
6701
+ severity: 'high',
6702
+ line: attrNode.loc?.start.line || 0,
6703
+ column: attrNode.loc?.start.column || 0,
6704
+ message: `Prop "${propName}" on component "${elementName}" expects type "number" but received a different type.`,
6705
+ code: `${propName}={...}`
6706
+ });
6707
+ }
6708
+ }
6709
+ }
6710
+ else if (propSpec.type === 'boolean') {
6711
+ // Check if value could be a boolean
6712
+ if (value && t.isJSXExpressionContainer(value)) {
6713
+ const expr = value.expression;
6714
+ if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
6715
+ t.isArrayExpression(expr) || t.isObjectExpression(expr)) {
6716
+ violations.push({
6717
+ rule: 'dependency-prop-validation',
6718
+ severity: 'high',
6719
+ line: attrNode.loc?.start.line || 0,
6720
+ column: attrNode.loc?.start.column || 0,
6721
+ message: `Prop "${propName}" on component "${elementName}" expects type "boolean" but received a different type.`,
6722
+ code: `${propName}={...}`
6723
+ });
6724
+ }
6725
+ }
6726
+ }
6727
+ else if (propSpec.type === 'array' || propSpec.type.startsWith('Array<')) {
6728
+ // Check if value could be an array
6729
+ if (value && t.isJSXExpressionContainer(value)) {
6730
+ const expr = value.expression;
6731
+ if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
6732
+ t.isBooleanLiteral(expr) || (t.isObjectExpression(expr) && !t.isArrayExpression(expr))) {
6733
+ violations.push({
6734
+ rule: 'dependency-prop-validation',
6735
+ severity: 'high',
6736
+ line: attrNode.loc?.start.line || 0,
6737
+ column: attrNode.loc?.start.column || 0,
6738
+ message: `Prop "${propName}" on component "${elementName}" expects type "array" but received a different type.`,
6739
+ code: `${propName}={...}`
6740
+ });
6741
+ }
6742
+ }
6743
+ }
6744
+ else if (propSpec.type === 'object') {
6745
+ // Check if value could be an object
6746
+ if (value && t.isJSXExpressionContainer(value)) {
6747
+ const expr = value.expression;
6748
+ if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
6749
+ t.isBooleanLiteral(expr) || t.isArrayExpression(expr)) {
6750
+ violations.push({
6751
+ rule: 'dependency-prop-validation',
6752
+ severity: 'high',
6753
+ line: attrNode.loc?.start.line || 0,
6754
+ column: attrNode.loc?.start.column || 0,
6755
+ message: `Prop "${propName}" on component "${elementName}" expects type "object" but received a different type.`,
6756
+ code: `${propName}={...}`
6757
+ });
6758
+ }
6759
+ }
6760
+ }
6818
6761
  }
6819
6762
  }
6820
- // Check for missing required props
6821
- for (const requiredProp of required) {
6822
- if (!passedProps.has(requiredProp) && !standardProps.has(requiredProp)) {
6763
+ }
6764
+ // ═══════════════════════════════════════════════════════════════
6765
+ // 3. CHECK UNKNOWN PROPS (with Levenshtein suggestions)
6766
+ // ═══════════════════════════════════════════════════════════════
6767
+ for (const passedProp of passedProps) {
6768
+ // Skip standard props and React special props
6769
+ if (standardProps.has(passedProp) || reactSpecialProps.has(passedProp)) {
6770
+ continue;
6771
+ }
6772
+ // Check if prop is valid (in properties or events)
6773
+ if (!allValidProps.includes(passedProp)) {
6774
+ // Try to find a close match using Levenshtein distance
6775
+ const suggestion = findClosestMatch(passedProp, allValidProps);
6776
+ const loc = passedPropNodes.get(passedProp);
6777
+ if (suggestion) {
6823
6778
  violations.push({
6824
6779
  rule: 'dependency-prop-validation',
6825
- severity: 'critical',
6826
- line: openingElement.loc?.start.line || 0,
6827
- column: openingElement.loc?.start.column || 0,
6828
- message: `Missing required prop '${requiredProp}' for dependency component '${elementName}'`,
6829
- code: `<${elementName} ... />`
6780
+ severity: 'high',
6781
+ line: loc?.loc?.start.line || openingElement.loc?.start.line || 0,
6782
+ column: loc?.loc?.start.column || openingElement.loc?.start.column || 0,
6783
+ message: `Unknown prop '${passedProp}' passed to dependency component '${elementName}'. Did you mean '${suggestion}'?`,
6784
+ code: `${passedProp}={...}`
6830
6785
  });
6831
6786
  }
6832
- }
6833
- // Check for unknown props (potential typos)
6834
- for (const passedProp of passedProps) {
6835
- // Skip standard props and spread operators
6836
- if (standardProps.has(passedProp) || passedProp === 'key' || passedProp === 'ref') {
6837
- continue;
6838
- }
6839
- if (!all.includes(passedProp)) {
6840
- // Try to find a close match
6841
- const suggestion = findClosestMatch(passedProp, all);
6842
- if (suggestion) {
6843
- const loc = propLocations.get(passedProp);
6844
- violations.push({
6845
- rule: 'dependency-prop-validation',
6846
- severity: 'high',
6847
- line: loc?.line || openingElement.loc?.start.line || 0,
6848
- column: loc?.column || openingElement.loc?.start.column || 0,
6849
- message: `Unknown prop '${passedProp}' passed to dependency component '${elementName}'. Did you mean '${suggestion}'?`,
6850
- code: `${passedProp}={...}`
6851
- });
6852
- }
6853
- else {
6854
- const loc = propLocations.get(passedProp);
6855
- violations.push({
6856
- rule: 'dependency-prop-validation',
6857
- severity: 'medium',
6858
- line: loc?.line || openingElement.loc?.start.line || 0,
6859
- column: loc?.column || openingElement.loc?.start.column || 0,
6860
- message: `Unknown prop '${passedProp}' passed to dependency component '${elementName}'. Expected props: ${all.join(', ')}`,
6861
- code: `${passedProp}={...}`
6862
- });
6863
- }
6787
+ else {
6788
+ violations.push({
6789
+ rule: 'dependency-prop-validation',
6790
+ severity: 'medium',
6791
+ line: loc?.loc?.start.line || openingElement.loc?.start.line || 0,
6792
+ column: loc?.loc?.start.column || openingElement.loc?.start.column || 0,
6793
+ message: `Unknown prop '${passedProp}' passed to dependency component '${elementName}'. Expected props and events: ${allValidProps.join(', ') || 'none'}.`,
6794
+ code: `${passedProp}={...}`
6795
+ });
6864
6796
  }
6865
6797
  }
6866
6798
  }