@memberjunction/react-test-harness 2.119.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.
|
@@ -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
|
});
|
|
@@ -5379,228 +5414,6 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
|
|
|
5379
5414
|
return violations;
|
|
5380
5415
|
}
|
|
5381
5416
|
},
|
|
5382
|
-
{
|
|
5383
|
-
name: 'validate-dependency-props',
|
|
5384
|
-
appliesTo: 'all',
|
|
5385
|
-
test: (ast, componentName, componentSpec) => {
|
|
5386
|
-
const violations = [];
|
|
5387
|
-
// Build a map of dependency components to their specs
|
|
5388
|
-
const dependencySpecs = new Map();
|
|
5389
|
-
// Process embedded dependencies
|
|
5390
|
-
if (componentSpec?.dependencies && Array.isArray(componentSpec.dependencies)) {
|
|
5391
|
-
for (const dep of componentSpec.dependencies) {
|
|
5392
|
-
if (dep && dep.name) {
|
|
5393
|
-
if (dep.location === 'registry') {
|
|
5394
|
-
const match = core_entities_1.ComponentMetadataEngine.Instance.FindComponent(dep.name, dep.namespace, dep.registry);
|
|
5395
|
-
if (!match) {
|
|
5396
|
-
// the specified registry component was not found, we can't lint for it, but we should put a warning
|
|
5397
|
-
console.warn('Dependency component not found in registry', dep);
|
|
5398
|
-
}
|
|
5399
|
-
else {
|
|
5400
|
-
dependencySpecs.set(dep.name, match.spec);
|
|
5401
|
-
}
|
|
5402
|
-
}
|
|
5403
|
-
else {
|
|
5404
|
-
// Embedded dependencies have their spec inline
|
|
5405
|
-
dependencySpecs.set(dep.name, dep);
|
|
5406
|
-
}
|
|
5407
|
-
}
|
|
5408
|
-
else {
|
|
5409
|
-
// we have an invalid dep in the spec, not a fatal error but we should log this
|
|
5410
|
-
console.warn(`Invalid dependency in component spec`, dep);
|
|
5411
|
-
}
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
// For registry dependencies, we'd need ComponentMetadataEngine
|
|
5415
|
-
// But since this is a static lint check, we'll focus on embedded deps
|
|
5416
|
-
// Registry components would need async loading which doesn't fit the current sync pattern
|
|
5417
|
-
// Now traverse JSX to find component usage
|
|
5418
|
-
(0, traverse_1.default)(ast, {
|
|
5419
|
-
JSXElement(path) {
|
|
5420
|
-
const openingElement = path.node.openingElement;
|
|
5421
|
-
// Check if this is one of our dependency components
|
|
5422
|
-
if (t.isJSXIdentifier(openingElement.name)) {
|
|
5423
|
-
const componentName = openingElement.name.name;
|
|
5424
|
-
const depSpec = dependencySpecs.get(componentName);
|
|
5425
|
-
if (depSpec) {
|
|
5426
|
-
// Collect props being passed
|
|
5427
|
-
const passedProps = new Set();
|
|
5428
|
-
const passedPropNodes = new Map();
|
|
5429
|
-
for (const attr of openingElement.attributes) {
|
|
5430
|
-
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
|
|
5431
|
-
const propName = attr.name.name;
|
|
5432
|
-
passedProps.add(propName);
|
|
5433
|
-
passedPropNodes.set(propName, attr);
|
|
5434
|
-
}
|
|
5435
|
-
}
|
|
5436
|
-
// Check required custom props
|
|
5437
|
-
if (depSpec.properties && Array.isArray(depSpec.properties)) {
|
|
5438
|
-
const requiredProps = [];
|
|
5439
|
-
const optionalProps = [];
|
|
5440
|
-
for (const prop of depSpec.properties) {
|
|
5441
|
-
if (prop && prop.name && typeof prop.name === 'string') {
|
|
5442
|
-
if (prop.required === true) {
|
|
5443
|
-
requiredProps.push(prop.name);
|
|
5444
|
-
}
|
|
5445
|
-
else {
|
|
5446
|
-
optionalProps.push(prop.name);
|
|
5447
|
-
}
|
|
5448
|
-
}
|
|
5449
|
-
}
|
|
5450
|
-
// Check for missing required props
|
|
5451
|
-
const missingRequired = requiredProps.filter(prop => {
|
|
5452
|
-
// Special handling for 'children' prop
|
|
5453
|
-
if (prop === 'children') {
|
|
5454
|
-
// Check if JSX element has children nodes
|
|
5455
|
-
const hasChildren = path.node.children && path.node.children.length > 0 &&
|
|
5456
|
-
path.node.children.some(child => !t.isJSXText(child) || (t.isJSXText(child) && child.value.trim() !== ''));
|
|
5457
|
-
return !passedProps.has(prop) && !hasChildren;
|
|
5458
|
-
}
|
|
5459
|
-
return !passedProps.has(prop);
|
|
5460
|
-
});
|
|
5461
|
-
// Separate children warnings from other critical props
|
|
5462
|
-
const missingChildren = missingRequired.filter(prop => prop === 'children');
|
|
5463
|
-
const missingOtherProps = missingRequired.filter(prop => prop !== 'children');
|
|
5464
|
-
// Critical violation for non-children required props
|
|
5465
|
-
if (missingOtherProps.length > 0) {
|
|
5466
|
-
violations.push({
|
|
5467
|
-
rule: 'validate-dependency-props',
|
|
5468
|
-
severity: 'critical',
|
|
5469
|
-
line: openingElement.loc?.start.line || 0,
|
|
5470
|
-
column: openingElement.loc?.start.column || 0,
|
|
5471
|
-
message: `Dependency component "${componentName}" is missing required props: ${missingOtherProps.join(', ')}. These props are marked as required in the component's specification.`,
|
|
5472
|
-
code: `<${componentName} ... />`
|
|
5473
|
-
});
|
|
5474
|
-
}
|
|
5475
|
-
// Medium severity warning for missing children when required
|
|
5476
|
-
if (missingChildren.length > 0) {
|
|
5477
|
-
violations.push({
|
|
5478
|
-
rule: 'validate-dependency-props',
|
|
5479
|
-
severity: 'medium',
|
|
5480
|
-
line: openingElement.loc?.start.line || 0,
|
|
5481
|
-
column: openingElement.loc?.start.column || 0,
|
|
5482
|
-
message: `Component "${componentName}" expects children but none were provided. The 'children' prop is marked as required in the component's specification.`,
|
|
5483
|
-
code: `<${componentName} ... />`
|
|
5484
|
-
});
|
|
5485
|
-
}
|
|
5486
|
-
// Validate prop types for passed props
|
|
5487
|
-
for (const [propName, attrNode] of passedPropNodes) {
|
|
5488
|
-
const propSpec = depSpec.properties.find(p => p.name === propName);
|
|
5489
|
-
if (propSpec && propSpec.type) {
|
|
5490
|
-
const value = attrNode.value;
|
|
5491
|
-
// Type validation based on prop spec type
|
|
5492
|
-
if (propSpec.type === 'string') {
|
|
5493
|
-
// Check if value could be a string
|
|
5494
|
-
if (value && t.isJSXExpressionContainer(value)) {
|
|
5495
|
-
const expr = value.expression;
|
|
5496
|
-
// Check for obvious non-string types
|
|
5497
|
-
if (t.isNumericLiteral(expr) || t.isBooleanLiteral(expr) ||
|
|
5498
|
-
t.isArrayExpression(expr) || (t.isObjectExpression(expr) && !t.isTemplateLiteral(expr))) {
|
|
5499
|
-
violations.push({
|
|
5500
|
-
rule: 'validate-dependency-props',
|
|
5501
|
-
severity: 'high',
|
|
5502
|
-
line: attrNode.loc?.start.line || 0,
|
|
5503
|
-
column: attrNode.loc?.start.column || 0,
|
|
5504
|
-
message: `Prop "${propName}" on component "${componentName}" expects type "string" but received a different type.`,
|
|
5505
|
-
code: `${propName}={...}`
|
|
5506
|
-
});
|
|
5507
|
-
}
|
|
5508
|
-
}
|
|
5509
|
-
}
|
|
5510
|
-
else if (propSpec.type === 'number') {
|
|
5511
|
-
// Check if value could be a number
|
|
5512
|
-
if (value && t.isJSXExpressionContainer(value)) {
|
|
5513
|
-
const expr = value.expression;
|
|
5514
|
-
if (t.isStringLiteral(expr) || t.isBooleanLiteral(expr) ||
|
|
5515
|
-
t.isArrayExpression(expr) || t.isObjectExpression(expr)) {
|
|
5516
|
-
violations.push({
|
|
5517
|
-
rule: 'validate-dependency-props',
|
|
5518
|
-
severity: 'high',
|
|
5519
|
-
line: attrNode.loc?.start.line || 0,
|
|
5520
|
-
column: attrNode.loc?.start.column || 0,
|
|
5521
|
-
message: `Prop "${propName}" on component "${componentName}" expects type "number" but received a different type.`,
|
|
5522
|
-
code: `${propName}={...}`
|
|
5523
|
-
});
|
|
5524
|
-
}
|
|
5525
|
-
}
|
|
5526
|
-
}
|
|
5527
|
-
else if (propSpec.type === 'boolean') {
|
|
5528
|
-
// Check if value could be a boolean
|
|
5529
|
-
if (value && t.isJSXExpressionContainer(value)) {
|
|
5530
|
-
const expr = value.expression;
|
|
5531
|
-
if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
|
|
5532
|
-
t.isArrayExpression(expr) || t.isObjectExpression(expr)) {
|
|
5533
|
-
violations.push({
|
|
5534
|
-
rule: 'validate-dependency-props',
|
|
5535
|
-
severity: 'high',
|
|
5536
|
-
line: attrNode.loc?.start.line || 0,
|
|
5537
|
-
column: attrNode.loc?.start.column || 0,
|
|
5538
|
-
message: `Prop "${propName}" on component "${componentName}" expects type "boolean" but received a different type.`,
|
|
5539
|
-
code: `${propName}={...}`
|
|
5540
|
-
});
|
|
5541
|
-
}
|
|
5542
|
-
}
|
|
5543
|
-
}
|
|
5544
|
-
else if (propSpec.type === 'array') {
|
|
5545
|
-
// Check if value could be an array
|
|
5546
|
-
if (value && t.isJSXExpressionContainer(value)) {
|
|
5547
|
-
const expr = value.expression;
|
|
5548
|
-
if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
|
|
5549
|
-
t.isBooleanLiteral(expr) || (t.isObjectExpression(expr) && !t.isArrayExpression(expr))) {
|
|
5550
|
-
violations.push({
|
|
5551
|
-
rule: 'validate-dependency-props',
|
|
5552
|
-
severity: 'high',
|
|
5553
|
-
line: attrNode.loc?.start.line || 0,
|
|
5554
|
-
column: attrNode.loc?.start.column || 0,
|
|
5555
|
-
message: `Prop "${propName}" on component "${componentName}" expects type "array" but received a different type.`,
|
|
5556
|
-
code: `${propName}={...}`
|
|
5557
|
-
});
|
|
5558
|
-
}
|
|
5559
|
-
}
|
|
5560
|
-
}
|
|
5561
|
-
else if (propSpec.type === 'object') {
|
|
5562
|
-
// Check if value could be an object
|
|
5563
|
-
if (value && t.isJSXExpressionContainer(value)) {
|
|
5564
|
-
const expr = value.expression;
|
|
5565
|
-
if (t.isStringLiteral(expr) || t.isNumericLiteral(expr) ||
|
|
5566
|
-
t.isBooleanLiteral(expr) || t.isArrayExpression(expr)) {
|
|
5567
|
-
violations.push({
|
|
5568
|
-
rule: 'validate-dependency-props',
|
|
5569
|
-
severity: 'high',
|
|
5570
|
-
line: attrNode.loc?.start.line || 0,
|
|
5571
|
-
column: attrNode.loc?.start.column || 0,
|
|
5572
|
-
message: `Prop "${propName}" on component "${componentName}" expects type "object" but received a different type.`,
|
|
5573
|
-
code: `${propName}={...}`
|
|
5574
|
-
});
|
|
5575
|
-
}
|
|
5576
|
-
}
|
|
5577
|
-
}
|
|
5578
|
-
}
|
|
5579
|
-
}
|
|
5580
|
-
// Check for unknown props (props not in the spec)
|
|
5581
|
-
const specPropNames = new Set(depSpec.properties.map(p => p.name).filter(Boolean));
|
|
5582
|
-
const standardProps = new Set(['utilities', 'styles', 'components', 'callbacks', 'savedUserSettings', 'onSaveUserSettings']);
|
|
5583
|
-
const reactSpecialProps = new Set(['children']);
|
|
5584
|
-
for (const passedProp of passedProps) {
|
|
5585
|
-
if (!specPropNames.has(passedProp) && !standardProps.has(passedProp) && !reactSpecialProps.has(passedProp)) {
|
|
5586
|
-
violations.push({
|
|
5587
|
-
rule: 'validate-dependency-props',
|
|
5588
|
-
severity: 'medium',
|
|
5589
|
-
line: passedPropNodes.get(passedProp)?.loc?.start.line || 0,
|
|
5590
|
-
column: passedPropNodes.get(passedProp)?.loc?.start.column || 0,
|
|
5591
|
-
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'}.`,
|
|
5592
|
-
code: `${passedProp}={...}`
|
|
5593
|
-
});
|
|
5594
|
-
}
|
|
5595
|
-
}
|
|
5596
|
-
}
|
|
5597
|
-
}
|
|
5598
|
-
}
|
|
5599
|
-
}
|
|
5600
|
-
});
|
|
5601
|
-
return violations;
|
|
5602
|
-
}
|
|
5603
|
-
},
|
|
5604
5417
|
{
|
|
5605
5418
|
name: 'unsafe-array-operations',
|
|
5606
5419
|
appliesTo: 'all',
|
|
@@ -6269,6 +6082,39 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
|
|
|
6269
6082
|
'response': 'Results',
|
|
6270
6083
|
'Response': 'Results'
|
|
6271
6084
|
};
|
|
6085
|
+
// Helper function to validate property access and create violation
|
|
6086
|
+
const validatePropertyAccess = (objName, propName, isFromRunQuery, isFromRunView, line, column, code) => {
|
|
6087
|
+
if (!isFromRunQuery && !isFromRunView)
|
|
6088
|
+
return;
|
|
6089
|
+
const isValidQueryProp = validRunQueryResultProps.has(propName);
|
|
6090
|
+
const isValidViewProp = validRunViewResultProps.has(propName);
|
|
6091
|
+
if (isFromRunQuery && !isValidQueryProp) {
|
|
6092
|
+
const suggestion = incorrectToCorrectMap[propName];
|
|
6093
|
+
violations.push({
|
|
6094
|
+
rule: 'runquery-result-invalid-property',
|
|
6095
|
+
severity: 'critical',
|
|
6096
|
+
line,
|
|
6097
|
+
column,
|
|
6098
|
+
message: suggestion
|
|
6099
|
+
? `RunQuery results don't have a ".${propName}" property. Use ".${suggestion}" instead. Change "${objName}.${propName}" to "${objName}.${suggestion}"`
|
|
6100
|
+
: `Invalid property "${propName}" on RunQuery result. Valid properties are: ${Array.from(validRunQueryResultProps).join(', ')}`,
|
|
6101
|
+
code
|
|
6102
|
+
});
|
|
6103
|
+
}
|
|
6104
|
+
else if (isFromRunView && !isValidViewProp) {
|
|
6105
|
+
const suggestion = incorrectToCorrectMap[propName];
|
|
6106
|
+
violations.push({
|
|
6107
|
+
rule: 'runview-result-invalid-property',
|
|
6108
|
+
severity: 'critical',
|
|
6109
|
+
line,
|
|
6110
|
+
column,
|
|
6111
|
+
message: suggestion
|
|
6112
|
+
? `RunView results don't have a ".${propName}" property. Use ".${suggestion}" instead. Change "${objName}.${propName}" to "${objName}.${suggestion}"`
|
|
6113
|
+
: `Invalid property "${propName}" on RunView result. Valid properties are: ${Array.from(validRunViewResultProps).join(', ')}`,
|
|
6114
|
+
code
|
|
6115
|
+
});
|
|
6116
|
+
}
|
|
6117
|
+
};
|
|
6272
6118
|
(0, traverse_1.default)(ast, {
|
|
6273
6119
|
MemberExpression(path) {
|
|
6274
6120
|
// Check if this is accessing a property on a variable that looks like a query/view result
|
|
@@ -6280,75 +6126,87 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
|
|
|
6280
6126
|
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunQuery');
|
|
6281
6127
|
const isFromRunView = path.scope.hasBinding(objName) &&
|
|
6282
6128
|
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
|
-
|
|
6129
|
+
// Use shared validation logic
|
|
6130
|
+
validatePropertyAccess(objName, propName, isFromRunQuery, isFromRunView, path.node.loc?.start.line || 0, path.node.loc?.start.column || 0, `${objName}.${propName}`);
|
|
6131
|
+
// Check for nested incorrect access like result.data.entities or result.Data.entities
|
|
6132
|
+
if ((isFromRunQuery || isFromRunView) &&
|
|
6133
|
+
t.isMemberExpression(path.parent) &&
|
|
6134
|
+
t.isIdentifier(path.parent.property) &&
|
|
6135
|
+
(propName === 'data' || propName === 'Data')) {
|
|
6136
|
+
const nestedProp = path.parent.property.name;
|
|
6137
|
+
violations.push({
|
|
6138
|
+
rule: 'runquery-runview-result-structure',
|
|
6139
|
+
severity: 'critical',
|
|
6140
|
+
line: path.parent.loc?.start.line || 0,
|
|
6141
|
+
column: path.parent.loc?.start.column || 0,
|
|
6142
|
+
message: `Incorrect nested property access "${objName}.${propName}.${nestedProp}". RunQuery/RunView results use ".Results" directly for the data array. Change to "${objName}.Results"`,
|
|
6143
|
+
code: `${objName}.${propName}.${nestedProp}`
|
|
6144
|
+
});
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
},
|
|
6148
|
+
// NEW: Handle optional chaining (result?.records, result?.Rows, etc.)
|
|
6149
|
+
OptionalMemberExpression(path) {
|
|
6150
|
+
if (t.isIdentifier(path.node.object) && t.isIdentifier(path.node.property)) {
|
|
6151
|
+
const objName = path.node.object.name;
|
|
6152
|
+
const propName = path.node.property.name;
|
|
6153
|
+
// Only check if we can definitively trace this to RunQuery or RunView
|
|
6154
|
+
const isFromRunQuery = path.scope.hasBinding(objName) &&
|
|
6155
|
+
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunQuery');
|
|
6156
|
+
const isFromRunView = path.scope.hasBinding(objName) &&
|
|
6157
|
+
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunView');
|
|
6158
|
+
// Use shared validation logic
|
|
6159
|
+
validatePropertyAccess(objName, propName, isFromRunQuery, isFromRunView, path.node.loc?.start.line || 0, path.node.loc?.start.column || 0, `${objName}?.${propName}`);
|
|
6160
|
+
}
|
|
6161
|
+
},
|
|
6162
|
+
// NEW: Detect weak fallback patterns like result?.records ?? result?.Rows ?? []
|
|
6163
|
+
LogicalExpression(path) {
|
|
6164
|
+
if (path.node.operator !== '??')
|
|
6165
|
+
return;
|
|
6166
|
+
// Collect all invalid property accesses in the chain
|
|
6167
|
+
const invalidAccesses = [];
|
|
6168
|
+
const checkNode = (node) => {
|
|
6169
|
+
if (t.isOptionalMemberExpression(node) &&
|
|
6170
|
+
t.isIdentifier(node.object) &&
|
|
6171
|
+
t.isIdentifier(node.property)) {
|
|
6172
|
+
const objName = node.object.name;
|
|
6173
|
+
const propName = node.property.name;
|
|
6174
|
+
// Check if this is from RunQuery/RunView
|
|
6175
|
+
const isFromRunQuery = path.scope.hasBinding(objName) &&
|
|
6176
|
+
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunQuery');
|
|
6177
|
+
const isFromRunView = path.scope.hasBinding(objName) &&
|
|
6178
|
+
ComponentLinter.isVariableFromRunQueryOrView(path, objName, 'RunView');
|
|
6179
|
+
if (isFromRunQuery || isFromRunView) {
|
|
6180
|
+
const isValidQueryProp = validRunQueryResultProps.has(propName);
|
|
6181
|
+
const isValidViewProp = validRunViewResultProps.has(propName);
|
|
6182
|
+
if ((isFromRunQuery && !isValidQueryProp) || (isFromRunView && !isValidViewProp)) {
|
|
6183
|
+
invalidAccesses.push({
|
|
6184
|
+
objName,
|
|
6185
|
+
propName,
|
|
6186
|
+
line: node.loc?.start.line || 0
|
|
6334
6187
|
});
|
|
6335
6188
|
}
|
|
6336
6189
|
}
|
|
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
6190
|
}
|
|
6191
|
+
else if (t.isLogicalExpression(node) && node.operator === '??') {
|
|
6192
|
+
// Recursively check chained ?? operators
|
|
6193
|
+
checkNode(node.left);
|
|
6194
|
+
checkNode(node.right);
|
|
6195
|
+
}
|
|
6196
|
+
};
|
|
6197
|
+
checkNode(path.node);
|
|
6198
|
+
// If we found multiple invalid accesses in a chain, report as weak fallback
|
|
6199
|
+
if (invalidAccesses.length >= 2) {
|
|
6200
|
+
const objName = invalidAccesses[0].objName;
|
|
6201
|
+
const props = invalidAccesses.map(a => a.propName).join(', ');
|
|
6202
|
+
violations.push({
|
|
6203
|
+
rule: 'runquery-runview-result-structure',
|
|
6204
|
+
severity: 'critical',
|
|
6205
|
+
line: path.node.loc?.start.line || 0,
|
|
6206
|
+
column: path.node.loc?.start.column || 0,
|
|
6207
|
+
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}".`,
|
|
6208
|
+
code: path.toString().substring(0, 100)
|
|
6209
|
+
});
|
|
6352
6210
|
}
|
|
6353
6211
|
},
|
|
6354
6212
|
// Check for destructuring patterns
|
|
@@ -6653,20 +6511,38 @@ Correct pattern:
|
|
|
6653
6511
|
if (!componentSpec?.dependencies || componentSpec.dependencies.length === 0) {
|
|
6654
6512
|
return violations;
|
|
6655
6513
|
}
|
|
6656
|
-
// Build a map of dependency components
|
|
6657
|
-
const
|
|
6514
|
+
// Build a map of dependency components to their full specs
|
|
6515
|
+
const dependencySpecs = new Map();
|
|
6516
|
+
// Process all dependencies (embedded and registry)
|
|
6658
6517
|
for (const dep of componentSpec.dependencies) {
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
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
|
+
}
|
|
6668
6544
|
}
|
|
6669
|
-
// Helper function to find closest matching prop name
|
|
6545
|
+
// Helper function to find closest matching prop name using Levenshtein distance
|
|
6670
6546
|
function findClosestMatch(target, candidates) {
|
|
6671
6547
|
if (candidates.length === 0)
|
|
6672
6548
|
return null;
|
|
@@ -6705,10 +6581,11 @@ Correct pattern:
|
|
|
6705
6581
|
}
|
|
6706
6582
|
// Standard props that are always valid (passed by the runtime)
|
|
6707
6583
|
const standardProps = new Set([
|
|
6708
|
-
'
|
|
6584
|
+
'utilities', 'styles', 'components', 'callbacks',
|
|
6709
6585
|
'savedUserSettings', 'onSaveUserSettings'
|
|
6710
6586
|
]);
|
|
6711
|
-
|
|
6587
|
+
const reactSpecialProps = new Set(['children', 'key', 'ref']);
|
|
6588
|
+
// Traverse JSX to find component usage
|
|
6712
6589
|
(0, traverse_1.default)(ast, {
|
|
6713
6590
|
JSXElement(path) {
|
|
6714
6591
|
const openingElement = path.node.openingElement;
|
|
@@ -6718,69 +6595,204 @@ Correct pattern:
|
|
|
6718
6595
|
elementName = openingElement.name.name;
|
|
6719
6596
|
}
|
|
6720
6597
|
else if (t.isJSXMemberExpression(openingElement.name)) {
|
|
6721
|
-
// Handle cases like <
|
|
6722
|
-
return;
|
|
6598
|
+
// Handle cases like <components.Button> - skip for now
|
|
6599
|
+
return;
|
|
6723
6600
|
}
|
|
6724
|
-
// Check if this is one of our
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
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
|
+
}
|
|
6738
6761
|
}
|
|
6739
6762
|
}
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
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) {
|
|
6743
6778
|
violations.push({
|
|
6744
6779
|
rule: 'dependency-prop-validation',
|
|
6745
|
-
severity: '
|
|
6746
|
-
line: openingElement.loc?.start.line || 0,
|
|
6747
|
-
column: openingElement.loc?.start.column || 0,
|
|
6748
|
-
message: `
|
|
6749
|
-
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}={...}`
|
|
6750
6785
|
});
|
|
6751
6786
|
}
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
const suggestion = findClosestMatch(passedProp, all);
|
|
6762
|
-
if (suggestion) {
|
|
6763
|
-
const loc = propLocations.get(passedProp);
|
|
6764
|
-
violations.push({
|
|
6765
|
-
rule: 'dependency-prop-validation',
|
|
6766
|
-
severity: 'high',
|
|
6767
|
-
line: loc?.line || openingElement.loc?.start.line || 0,
|
|
6768
|
-
column: loc?.column || openingElement.loc?.start.column || 0,
|
|
6769
|
-
message: `Unknown prop '${passedProp}' passed to dependency component '${elementName}'. Did you mean '${suggestion}'?`,
|
|
6770
|
-
code: `${passedProp}={...}`
|
|
6771
|
-
});
|
|
6772
|
-
}
|
|
6773
|
-
else {
|
|
6774
|
-
const loc = propLocations.get(passedProp);
|
|
6775
|
-
violations.push({
|
|
6776
|
-
rule: 'dependency-prop-validation',
|
|
6777
|
-
severity: 'medium',
|
|
6778
|
-
line: loc?.line || openingElement.loc?.start.line || 0,
|
|
6779
|
-
column: loc?.column || openingElement.loc?.start.column || 0,
|
|
6780
|
-
message: `Unknown prop '${passedProp}' passed to dependency component '${elementName}'. Expected props: ${all.join(', ')}`,
|
|
6781
|
-
code: `${passedProp}={...}`
|
|
6782
|
-
});
|
|
6783
|
-
}
|
|
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
|
+
});
|
|
6784
6796
|
}
|
|
6785
6797
|
}
|
|
6786
6798
|
}
|