@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,
|
|
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
|
|
6737
|
-
const
|
|
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
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
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
|
-
'
|
|
6584
|
+
'utilities', 'styles', 'components', 'callbacks',
|
|
6789
6585
|
'savedUserSettings', 'onSaveUserSettings'
|
|
6790
6586
|
]);
|
|
6791
|
-
|
|
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 <
|
|
6802
|
-
return;
|
|
6598
|
+
// Handle cases like <components.Button> - skip for now
|
|
6599
|
+
return;
|
|
6803
6600
|
}
|
|
6804
|
-
// Check if this is one of our
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
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
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
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: '
|
|
6826
|
-
line: openingElement.loc?.start.line || 0,
|
|
6827
|
-
column: openingElement.loc?.start.column || 0,
|
|
6828
|
-
message: `
|
|
6829
|
-
code:
|
|
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
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
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
|
}
|