@memberjunction/react-test-harness 2.96.0 → 2.98.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.
@@ -196,6 +196,10 @@ class ComponentLinter {
196
196
  }
197
197
  static async lintComponent(code, componentName, componentSpec, isRootComponent, contextUser, debugMode, options) {
198
198
  try {
199
+ // Require contextUser when libraries need to be checked
200
+ if (componentSpec?.libraries && componentSpec.libraries.length > 0 && !contextUser) {
201
+ throw new Error('contextUser is required when linting components with library dependencies. This is needed to load library-specific lint rules from the database.');
202
+ }
199
203
  // Parse with error recovery to get both AST and errors
200
204
  const parseResult = parser.parse(code, {
201
205
  sourceType: 'module',
@@ -222,14 +226,16 @@ class ComponentLinter {
222
226
  }
223
227
  // If we have critical syntax errors, return immediately with those
224
228
  if (syntaxViolations.length > 0) {
229
+ // Add suggestions directly to syntax violations
230
+ this.generateSyntaxErrorSuggestions(syntaxViolations);
225
231
  return {
226
232
  success: false,
227
233
  violations: syntaxViolations,
228
- suggestions: this.generateSyntaxErrorSuggestions(syntaxViolations),
229
234
  criticalCount: syntaxViolations.length,
230
235
  highCount: 0,
231
236
  mediumCount: 0,
232
- lowCount: 0
237
+ lowCount: 0,
238
+ hasErrors: true
233
239
  };
234
240
  }
235
241
  // Continue with existing linting logic
@@ -257,7 +263,7 @@ class ComponentLinter {
257
263
  violations.push(...dataViolations);
258
264
  }
259
265
  // Apply library-specific lint rules if available
260
- if (componentSpec?.libraries && contextUser) {
266
+ if (componentSpec?.libraries) {
261
267
  const libraryViolations = await this.applyLibraryLintRules(ast, componentSpec, contextUser, debugMode);
262
268
  violations.push(...libraryViolations);
263
269
  }
@@ -299,16 +305,16 @@ class ComponentLinter {
299
305
  }
300
306
  console.log('');
301
307
  }
302
- // Generate fix suggestions
303
- const suggestions = this.generateFixSuggestions(uniqueViolations);
308
+ // Add suggestions directly to violations
309
+ this.addSuggestionsToViolations(uniqueViolations);
304
310
  return {
305
311
  success: criticalCount === 0 && highCount === 0, // Only fail on critical/high
306
312
  violations: uniqueViolations,
307
- suggestions,
308
313
  criticalCount,
309
314
  highCount,
310
315
  mediumCount,
311
- lowCount
316
+ lowCount,
317
+ hasErrors: criticalCount > 0 || highCount > 0
312
318
  };
313
319
  }
314
320
  catch (error) {
@@ -322,7 +328,7 @@ class ComponentLinter {
322
328
  column: 0,
323
329
  message: `Failed to parse component: ${error instanceof Error ? error.message : 'Unknown error'}`
324
330
  }],
325
- suggestions: []
331
+ hasErrors: true
326
332
  };
327
333
  }
328
334
  }
@@ -690,14 +696,17 @@ class ComponentLinter {
690
696
  });
691
697
  return unique;
692
698
  }
693
- static generateFixSuggestions(violations) {
694
- const suggestions = [];
699
+ /**
700
+ * Adds suggestions directly to violations based on their rule type
701
+ * @param violations Array of violations to enhance with suggestions
702
+ * @returns The same violations array with suggestions embedded
703
+ */
704
+ static addSuggestionsToViolations(violations) {
695
705
  for (const violation of violations) {
696
706
  switch (violation.rule) {
697
707
  case 'no-import-statements':
698
- suggestions.push({
699
- violation: violation.rule,
700
- suggestion: 'Remove all import statements. Interactive components receive everything through props.',
708
+ violation.suggestion = {
709
+ text: 'Remove all import statements. Interactive components receive everything through props.',
701
710
  example: `// ❌ WRONG - Using import statements:
702
711
  import React from 'react';
703
712
  import { useState } from 'react';
@@ -725,12 +734,11 @@ function MyComponent({ utilities, styles, components }) {
725
734
  // 2. Passed through the 'components' prop (child components)
726
735
  // 3. Passed through the 'styles' prop (styling)
727
736
  // 4. Available globally (React hooks)`
728
- });
737
+ };
729
738
  break;
730
739
  case 'no-export-statements':
731
- suggestions.push({
732
- violation: violation.rule,
733
- suggestion: 'Remove all export statements. The component function should be the only code, not exported.',
740
+ violation.suggestion = {
741
+ text: 'Remove all export statements. The component function should be the only code, not exported.',
734
742
  example: `// ❌ WRONG - Using export:
735
743
  export function MyComponent({ utilities }) {
736
744
  return <div>Hello</div>;
@@ -752,12 +760,11 @@ function MyComponent({ utilities, styles, components }) {
752
760
  // The component is self-contained.
753
761
  // No exports needed - the host environment
754
762
  // will execute the function directly.`
755
- });
763
+ };
756
764
  break;
757
765
  case 'no-require-statements':
758
- suggestions.push({
759
- violation: violation.rule,
760
- suggestion: 'Remove all require() and dynamic import() statements. Use props instead.',
766
+ violation.suggestion = {
767
+ text: 'Remove all require() and dynamic import() statements. Use props instead.',
761
768
  example: `// ❌ WRONG - Using require or dynamic import:
762
769
  function MyComponent({ utilities }) {
763
770
  const lodash = require('lodash');
@@ -788,12 +795,11 @@ function MyComponent({ utilities, styles, components }) {
788
795
  // - Passed via props (utilities, components, styles)
789
796
  // - Available globally (React hooks)
790
797
  // No module loading allowed!`
791
- });
798
+ };
792
799
  break;
793
800
  case 'use-function-declaration':
794
- suggestions.push({
795
- violation: violation.rule,
796
- suggestion: 'Use function declaration syntax for TOP-LEVEL component definitions. Arrow functions are fine inside components.',
801
+ violation.suggestion = {
802
+ text: 'Use function declaration syntax for TOP-LEVEL component definitions. Arrow functions are fine inside components.',
797
803
  example: `// ❌ WRONG - Top-level arrow function component:
798
804
  const MyComponent = ({ utilities, styles, components }) => {
799
805
  const [state, setState] = useState('');
@@ -826,12 +832,11 @@ function ChildComponent() {
826
832
  // 3. Hoisting allows flexible code organization
827
833
  // 4. Consistent with React documentation patterns
828
834
  // 5. Easier to distinguish from regular variables`
829
- });
835
+ };
830
836
  break;
831
837
  case 'no-return-component':
832
- suggestions.push({
833
- violation: violation.rule,
834
- suggestion: 'Remove the return statement at the end of the file. The component function should stand alone.',
838
+ violation.suggestion = {
839
+ text: 'Remove the return statement at the end of the file. The component function should stand alone.',
835
840
  example: `// ❌ WRONG - Returning the component:
836
841
  function MyComponent({ utilities, styles, components }) {
837
842
  const [state, setState] = useState('');
@@ -858,12 +863,11 @@ function MyComponent({ utilities, styles, components }) {
858
863
 
859
864
  // The runtime will find and execute your component
860
865
  // by its function name. No need to return or reference it!`
861
- });
866
+ };
862
867
  break;
863
868
  case 'no-iife-wrapper':
864
- suggestions.push({
865
- violation: violation.rule,
866
- suggestion: 'Remove the IIFE wrapper. Component code should be plain functions, not wrapped in immediately invoked functions.',
869
+ violation.suggestion = {
870
+ text: 'Remove the IIFE wrapper. Component code should be plain functions, not wrapped in immediately invoked functions.',
867
871
  example: `// ❌ WRONG - IIFE wrapper patterns:
868
872
  (function() {
869
873
  function MyComponent({ utilities, styles, components }) {
@@ -897,12 +901,11 @@ function MyComponent({ utilities, styles, components }) {
897
901
  // 3. IIFEs prevent proper component discovery
898
902
  // 4. Makes debugging harder
899
903
  // 5. Unnecessary complexity`
900
- });
904
+ };
901
905
  break;
902
906
  case 'full-state-ownership':
903
- suggestions.push({
904
- violation: violation.rule,
905
- suggestion: 'Components must manage ALL their own state internally. Use proper naming conventions for initialization.',
907
+ violation.suggestion = {
908
+ text: 'Components must manage ALL their own state internally. Use proper naming conventions for initialization.',
906
909
  example: `// ❌ WRONG - Controlled state props:
907
910
  function PaginationControls({ currentPage, filters, sortBy, onPageChange }) {
908
911
  // These props suggest parent controls the state - WRONG!
@@ -962,12 +965,11 @@ function DataTable({
962
965
  // - Direct state names (currentPage, selectedId, activeTab)
963
966
  // - State without 'initial'/'default' prefix (sortBy, filters, searchTerm)
964
967
  // - Controlled patterns (value + onChange, checked + onChange)`
965
- });
968
+ };
966
969
  break;
967
970
  case 'no-use-reducer':
968
- suggestions.push({
969
- violation: violation.rule,
970
- suggestion: 'Use useState for state management, not useReducer',
971
+ violation.suggestion = {
972
+ text: 'Use useState for state management, not useReducer',
971
973
  example: `// Instead of:
972
974
  const [state, dispatch] = useReducer(reducer, initialState);
973
975
 
@@ -990,12 +992,11 @@ function Component({ savedUserSettings, onSaveUserSettings }) {
990
992
  }
991
993
  };
992
994
  }`
993
- });
995
+ };
994
996
  break;
995
997
  case 'no-data-prop':
996
- suggestions.push({
997
- violation: violation.rule,
998
- suggestion: 'Replace generic data prop with specific named props',
998
+ violation.suggestion = {
999
+ text: 'Replace generic data prop with specific named props',
999
1000
  example: `// Instead of:
1000
1001
  function Component({ data, savedUserSettings, onSaveUserSettings }) {
1001
1002
  return <div>{data.items.map(...)}</div>;
@@ -1013,12 +1014,11 @@ function Component({ items, customers, savedUserSettings, onSaveUserSettings })
1013
1014
 
1014
1015
  // Load data using utilities:
1015
1016
  const result = await utilities.rv.RunView({ entityName: 'Items' });`
1016
- });
1017
+ };
1017
1018
  break;
1018
1019
  case 'saved-user-settings-pattern':
1019
- suggestions.push({
1020
- violation: violation.rule,
1021
- suggestion: 'Only save important user preferences, not ephemeral UI state',
1020
+ violation.suggestion = {
1021
+ text: 'Only save important user preferences, not ephemeral UI state',
1022
1022
  example: `// ✅ SAVE these (important preferences):
1023
1023
  - Selected items/tabs: selectedCustomerId, activeTab
1024
1024
  - Sort preferences: sortBy, sortDirection
@@ -1043,12 +1043,11 @@ const handleSelect = (id) => {
1043
1043
  selectedId: id
1044
1044
  });
1045
1045
  };`
1046
- });
1046
+ };
1047
1047
  break;
1048
1048
  case 'pass-standard-props':
1049
- suggestions.push({
1050
- violation: violation.rule,
1051
- suggestion: 'Always pass standard props to all components',
1049
+ violation.suggestion = {
1050
+ text: 'Always pass standard props to all components',
1052
1051
  example: `// Always include these props when calling components:
1053
1052
  <ChildComponent
1054
1053
  items={items} // Data props
@@ -1063,19 +1062,17 @@ const handleSelect = (id) => {
1063
1062
  components={components}
1064
1063
  callbacks={callbacks}
1065
1064
  />`
1066
- });
1065
+ };
1067
1066
  break;
1068
1067
  case 'no-child-implementation':
1069
- suggestions.push({
1070
- violation: violation.rule,
1071
- suggestion: 'Remove child component implementations. Only the root component function should be in this file',
1068
+ violation.suggestion = {
1069
+ text: 'Remove child component implementations. Only the root component function should be in this file',
1072
1070
  example: 'Move child component functions to separate generation requests'
1073
- });
1071
+ };
1074
1072
  break;
1075
1073
  case 'undefined-component-usage':
1076
- suggestions.push({
1077
- violation: violation.rule,
1078
- suggestion: 'Ensure all components destructured from the components prop are defined in the component spec dependencies',
1074
+ violation.suggestion = {
1075
+ text: 'Ensure all components destructured from the components prop are defined in the component spec dependencies',
1079
1076
  example: `// Component spec should include all referenced components:
1080
1077
  {
1081
1078
  "name": "MyComponent",
@@ -1100,12 +1097,11 @@ const handleSelect = (id) => {
1100
1097
  // Then in your component:
1101
1098
  const { ModelTreeView, PromptTable, FilterPanel } = components;
1102
1099
  // All these will be available`
1103
- });
1100
+ };
1104
1101
  break;
1105
1102
  case 'component-usage-without-destructuring':
1106
- suggestions.push({
1107
- violation: violation.rule,
1108
- suggestion: 'Components must be properly accessed - either destructure from components prop or use dot notation',
1103
+ violation.suggestion = {
1104
+ text: 'Components must be properly accessed - either destructure from components prop or use dot notation',
1109
1105
  example: `// ❌ WRONG - Using component without destructuring:
1110
1106
  function MyComponent({ components }) {
1111
1107
  return <AccountList />; // Error: AccountList not destructured
@@ -1126,12 +1122,11 @@ function MyComponent({ components }) {
1126
1122
  function MyComponent({ components: { AccountList } }) {
1127
1123
  return <AccountList />;
1128
1124
  }`
1129
- });
1125
+ };
1130
1126
  break;
1131
1127
  case 'unsafe-array-access':
1132
- suggestions.push({
1133
- violation: violation.rule,
1134
- suggestion: 'Always check array bounds before accessing elements',
1128
+ violation.suggestion = {
1129
+ text: 'Always check array bounds before accessing elements',
1135
1130
  example: `// ❌ UNSAFE:
1136
1131
  const firstItem = items[0].name;
1137
1132
  const total = data[0].reduce((sum, item) => sum + item.value, 0);
@@ -1145,12 +1140,11 @@ const total = data.length > 0
1145
1140
  // ✅ BETTER - Use optional chaining:
1146
1141
  const firstItem = items[0]?.name || 'No items';
1147
1142
  const total = data[0]?.reduce((sum, item) => sum + item.value, 0) || 0;`
1148
- });
1143
+ };
1149
1144
  break;
1150
1145
  case 'array-reduce-safety':
1151
- suggestions.push({
1152
- violation: violation.rule,
1153
- suggestion: 'Always provide an initial value for reduce() or check array length',
1146
+ violation.suggestion = {
1147
+ text: 'Always provide an initial value for reduce() or check array length',
1154
1148
  example: `// ❌ UNSAFE:
1155
1149
  const sum = numbers.reduce((a, b) => a + b); // Fails on empty array
1156
1150
  const total = data[0].reduce((sum, item) => sum + item.value); // Multiple issues
@@ -1165,12 +1159,11 @@ const total = data.length > 0 && data[0]
1165
1159
  const sum = numbers.length > 0
1166
1160
  ? numbers.reduce((a, b) => a + b)
1167
1161
  : 0;`
1168
- });
1162
+ };
1169
1163
  break;
1170
1164
  case 'entity-name-mismatch':
1171
- suggestions.push({
1172
- violation: violation.rule,
1173
- suggestion: 'Use the exact entity name from dataRequirements in RunView calls',
1165
+ violation.suggestion = {
1166
+ text: 'Use the exact entity name from dataRequirements in RunView calls',
1174
1167
  example: `// The component spec defines the entities to use:
1175
1168
  // dataRequirements: {
1176
1169
  // entities: [
@@ -1198,12 +1191,11 @@ await utilities.rv.RunViews([
1198
1191
 
1199
1192
  // The linter validates that all entity names in RunView/RunViews calls
1200
1193
  // match those declared in the component spec's dataRequirements`
1201
- });
1194
+ };
1202
1195
  break;
1203
1196
  case 'missing-query-parameter':
1204
- suggestions.push({
1205
- violation: violation.rule,
1206
- suggestion: 'Provide all required parameters defined in dataRequirements for the query',
1197
+ violation.suggestion = {
1198
+ text: 'Provide all required parameters defined in dataRequirements for the query',
1207
1199
  example: `// The component spec defines required parameters:
1208
1200
  // dataRequirements: {
1209
1201
  // queries: [
@@ -1234,12 +1226,11 @@ await utilities.rq.RunQuery({
1234
1226
  StartDate: startDate // All parameters included
1235
1227
  }
1236
1228
  });`
1237
- });
1229
+ };
1238
1230
  break;
1239
1231
  case 'unknown-query-parameter':
1240
- suggestions.push({
1241
- violation: violation.rule,
1242
- suggestion: 'Only use parameters that are defined in dataRequirements for the query',
1232
+ violation.suggestion = {
1233
+ text: 'Only use parameters that are defined in dataRequirements for the query',
1243
1234
  example: `// ❌ WRONG - Using undefined parameter:
1244
1235
  await utilities.rq.RunQuery({
1245
1236
  QueryName: "User Activity Summary",
@@ -1258,12 +1249,11 @@ await utilities.rq.RunQuery({
1258
1249
  StartDate: startDate // Only parameters from dataRequirements
1259
1250
  }
1260
1251
  });`
1261
- });
1252
+ };
1262
1253
  break;
1263
1254
  case 'missing-parameters-object':
1264
- suggestions.push({
1265
- violation: violation.rule,
1266
- suggestion: 'Queries with parameters must include a Parameters object in RunQuery',
1255
+ violation.suggestion = {
1256
+ text: 'Queries with parameters must include a Parameters object in RunQuery',
1267
1257
  example: `// ❌ WRONG - Query requires parameters but none provided:
1268
1258
  await utilities.rq.RunQuery({
1269
1259
  QueryName: "User Activity Summary"
@@ -1278,12 +1268,11 @@ await utilities.rq.RunQuery({
1278
1268
  StartDate: startDate
1279
1269
  }
1280
1270
  });`
1281
- });
1271
+ };
1282
1272
  break;
1283
1273
  case 'query-name-mismatch':
1284
- suggestions.push({
1285
- violation: violation.rule,
1286
- suggestion: 'Use the exact query name from dataRequirements in RunQuery calls',
1274
+ violation.suggestion = {
1275
+ text: 'Use the exact query name from dataRequirements in RunQuery calls',
1287
1276
  example: `// The component spec defines the queries to use:
1288
1277
  // dataRequirements: {
1289
1278
  // queries: [
@@ -1305,12 +1294,11 @@ await utilities.rv.RunQuery({
1305
1294
 
1306
1295
  // The linter validates that all query names in RunQuery calls
1307
1296
  // match those declared in the component spec's dataRequirements.queries`
1308
- });
1297
+ };
1309
1298
  break;
1310
1299
  case 'runview-sql-function':
1311
- suggestions.push({
1312
- violation: violation.rule,
1313
- suggestion: 'RunView does not support SQL aggregations. Use RunQuery or aggregate in JavaScript.',
1300
+ violation.suggestion = {
1301
+ text: 'RunView does not support SQL aggregations. Use RunQuery or aggregate in JavaScript.',
1314
1302
  example: `// ❌ WRONG - SQL functions in RunView:
1315
1303
  await utilities.rv.RunView({
1316
1304
  EntityName: 'Accounts',
@@ -1332,12 +1320,11 @@ if (result?.Success) {
1332
1320
  const total = result.Results.length;
1333
1321
  const totalRevenue = result.Results.reduce((sum, acc) => sum + (acc.Revenue || 0), 0);
1334
1322
  }`
1335
- });
1323
+ };
1336
1324
  break;
1337
1325
  case 'field-not-in-requirements':
1338
- suggestions.push({
1339
- violation: violation.rule,
1340
- suggestion: 'Only use fields that are defined in dataRequirements for the entity',
1326
+ violation.suggestion = {
1327
+ text: 'Only use fields that are defined in dataRequirements for the entity',
1341
1328
  example: `// Check your dataRequirements to see allowed fields:
1342
1329
  // dataRequirements: {
1343
1330
  // entities: [{
@@ -1359,12 +1346,11 @@ await utilities.rv.RunView({
1359
1346
  EntityName: 'Accounts',
1360
1347
  Fields: ['ID', 'AccountName', 'Industry'] // All from displayFields
1361
1348
  });`
1362
- });
1349
+ };
1363
1350
  break;
1364
1351
  case 'orderby-field-not-sortable':
1365
- suggestions.push({
1366
- violation: violation.rule,
1367
- suggestion: 'OrderBy fields must be in the sortFields array for the entity',
1352
+ violation.suggestion = {
1353
+ text: 'OrderBy fields must be in the sortFields array for the entity',
1368
1354
  example: `// ❌ WRONG - Sorting by non-sortable field:
1369
1355
  await utilities.rv.RunView({
1370
1356
  EntityName: 'Accounts',
@@ -1376,12 +1362,11 @@ await utilities.rv.RunView({
1376
1362
  EntityName: 'Accounts',
1377
1363
  OrderBy: 'AccountName ASC' // AccountName is in sortFields
1378
1364
  });`
1379
- });
1365
+ };
1380
1366
  break;
1381
1367
  case 'parent-event-callback-usage':
1382
- suggestions.push({
1383
- violation: violation.rule,
1384
- suggestion: 'Components must invoke parent event callbacks when state changes',
1368
+ violation.suggestion = {
1369
+ text: 'Components must invoke parent event callbacks when state changes',
1385
1370
  example: `// ❌ WRONG - Only updating internal state:
1386
1371
  function ChildComponent({ onSelectAccount, savedUserSettings, onSaveUserSettings }) {
1387
1372
  const [selectedAccountId, setSelectedAccountId] = useState(savedUserSettings?.selectedAccountId);
@@ -1410,12 +1395,11 @@ function ChildComponent({ onSelectAccount, savedUserSettings, onSaveUserSettings
1410
1395
  onSaveUserSettings?.({ ...savedUserSettings, selectedAccountId: accountId });
1411
1396
  };
1412
1397
  }`
1413
- });
1398
+ };
1414
1399
  break;
1415
1400
  case 'property-name-consistency':
1416
- suggestions.push({
1417
- violation: violation.rule,
1418
- suggestion: 'Maintain consistent property names when transforming data',
1401
+ violation.suggestion = {
1402
+ text: 'Maintain consistent property names when transforming data',
1419
1403
  example: `// ❌ WRONG - Transform to camelCase but access as PascalCase:
1420
1404
  setAccountData(results.map(item => ({
1421
1405
  accountName: item.AccountName, // camelCase
@@ -1442,12 +1426,11 @@ setAccountData(results.map(item => ({
1442
1426
  // Later in render...
1443
1427
  <td>{account.accountName}</td> // Use camelCase consistently
1444
1428
  <td>{formatCurrency(account.annualRevenue)}</td> // Works!`
1445
- });
1429
+ };
1446
1430
  break;
1447
1431
  case 'noisy-settings-updates':
1448
- suggestions.push({
1449
- violation: violation.rule,
1450
- suggestion: 'Save settings sparingly - only on meaningful user actions',
1432
+ violation.suggestion = {
1433
+ text: 'Save settings sparingly - only on meaningful user actions',
1451
1434
  example: `// ❌ WRONG - Saving on every keystroke:
1452
1435
  const handleSearchChange = (e) => {
1453
1436
  setSearchTerm(e.target.value);
@@ -1468,12 +1451,11 @@ const saveSearchTerm = useMemo(() =>
1468
1451
  }, 500),
1469
1452
  [savedUserSettings]
1470
1453
  );`
1471
- });
1454
+ };
1472
1455
  break;
1473
1456
  case 'prop-state-sync':
1474
- suggestions.push({
1475
- violation: violation.rule,
1476
- suggestion: 'Initialize state once, don\'t sync from props',
1457
+ violation.suggestion = {
1458
+ text: 'Initialize state once, don\'t sync from props',
1477
1459
  example: `// ❌ WRONG - Syncing prop to state:
1478
1460
  const [value, setValue] = useState(propValue);
1479
1461
  useEffect(() => {
@@ -1487,12 +1469,11 @@ const [value, setValue] = useState(
1487
1469
 
1488
1470
  // ✅ CORRECT - If you need prop changes, use derived state:
1489
1471
  const displayValue = propOverride || value;`
1490
- });
1472
+ };
1491
1473
  break;
1492
1474
  case 'performance-memoization':
1493
- suggestions.push({
1494
- violation: violation.rule,
1495
- suggestion: 'Use useMemo for expensive operations and static data',
1475
+ violation.suggestion = {
1476
+ text: 'Use useMemo for expensive operations and static data',
1496
1477
  example: `// ❌ WRONG - Expensive operation on every render:
1497
1478
  const filteredItems = items.filter(item =>
1498
1479
  item.name.toLowerCase().includes(searchTerm.toLowerCase())
@@ -1517,12 +1498,11 @@ const columns = useMemo(() => [
1517
1498
  { field: 'name', header: 'Name' },
1518
1499
  { field: 'value', header: 'Value' }
1519
1500
  ], []); // Empty deps = never changes`
1520
- });
1501
+ };
1521
1502
  break;
1522
1503
  case 'child-state-management':
1523
- suggestions.push({
1524
- violation: violation.rule,
1525
- suggestion: 'Never manage state for child components',
1504
+ violation.suggestion = {
1505
+ text: 'Never manage state for child components',
1526
1506
  example: `// ❌ WRONG - Managing child state:
1527
1507
  const [childTableSort, setChildTableSort] = useState('name');
1528
1508
  const [modalOpen, setModalOpen] = useState(false);
@@ -1539,12 +1519,11 @@ const [modalOpen, setModalOpen] = useState(false);
1539
1519
  onSaveUserSettings={handleChildSettings}
1540
1520
  // Child manages its own sort state!
1541
1521
  />`
1542
- });
1522
+ };
1543
1523
  break;
1544
1524
  case 'server-reload-on-client-operation':
1545
- suggestions.push({
1546
- violation: violation.rule,
1547
- suggestion: 'Use client-side operations for sorting and filtering',
1525
+ violation.suggestion = {
1526
+ text: 'Use client-side operations for sorting and filtering',
1548
1527
  example: `// ❌ WRONG - Reload from server:
1549
1528
  const handleSort = (field) => {
1550
1529
  setSortBy(field);
@@ -1568,12 +1547,11 @@ const sortedData = useMemo(() => {
1568
1547
  });
1569
1548
  return sorted;
1570
1549
  }, [data, sortBy, sortDirection]);`
1571
- });
1550
+ };
1572
1551
  break;
1573
1552
  case 'runview-runquery-valid-properties':
1574
- suggestions.push({
1575
- violation: violation.rule,
1576
- suggestion: 'Use only valid properties for RunView/RunViews and RunQuery',
1553
+ violation.suggestion = {
1554
+ text: 'Use only valid properties for RunView/RunViews and RunQuery',
1577
1555
  example: `// ❌ WRONG - Invalid properties on RunView:
1578
1556
  await utilities.rv.RunView({
1579
1557
  EntityName: 'MJ: AI Prompt Runs',
@@ -1602,23 +1580,34 @@ await utilities.rq.RunQuery({
1602
1580
  // Valid RunQuery properties:
1603
1581
  // - QueryName (required)
1604
1582
  // - CategoryName, CategoryID, Parameters (optional)`
1605
- });
1583
+ };
1606
1584
  break;
1607
1585
  case 'component-props-validation':
1608
- suggestions.push({
1609
- violation: violation.rule,
1610
- suggestion: 'Components can only accept standard props or props defined in spec. Load data internally.',
1611
- example: `// WRONG - Root component with additional props:
1612
- function RootComponent({ utilities, styles, components, customers, orders, selectedId }) {
1613
- // Additional props will break hosting environment
1586
+ violation.suggestion = {
1587
+ text: 'Components can only accept standard props and props explicitly defined in the component spec. Additional props must be declared in the spec\'s properties array.',
1588
+ example: `// WRONG - Component with undeclared props:
1589
+ function MyComponent({ utilities, styles, components, customers, orders, selectedId }) {
1590
+ // customers, orders, selectedId are NOT allowed unless defined in spec
1614
1591
  }
1615
1592
 
1616
- // ✅ CORRECT - Root component with only standard props:
1617
- function RootComponent({ utilities, styles, components, callbacks, savedUserSettings, onSaveUserSettings }) {
1618
- // Load ALL data internally using utilities
1593
+ // ✅ CORRECT Option 1 - Use only standard props and load data internally:
1594
+ function MyComponent({ utilities, styles, components, callbacks, savedUserSettings, onSaveUserSettings }) {
1595
+ // Load data internally using utilities
1619
1596
  const [customers, setCustomers] = useState([]);
1620
1597
  const [orders, setOrders] = useState([]);
1621
1598
  const [selectedId, setSelectedId] = useState(savedUserSettings?.selectedId);
1599
+ }
1600
+
1601
+ // ✅ CORRECT Option 2 - Define props in component spec:
1602
+ // In spec.properties array:
1603
+ // [
1604
+ // { name: "customers", type: "array", required: false, description: "Customer list" },
1605
+ // { name: "orders", type: "array", required: false, description: "Order list" },
1606
+ // { name: "selectedId", type: "string", required: false, description: "Selected item ID" }
1607
+ // ]
1608
+ // Then the component can accept them:
1609
+ function MyComponent({ utilities, styles, components, customers, orders, selectedId }) {
1610
+ // These props are now allowed because they're defined in the spec
1622
1611
 
1623
1612
  useEffect(() => {
1624
1613
  const loadData = async () => {
@@ -1639,12 +1628,11 @@ function RootComponent({ utilities, styles, components, callbacks, savedUserSett
1639
1628
 
1640
1629
  return <div>{/* Use state, not props */}</div>;
1641
1630
  }`
1642
- });
1631
+ };
1643
1632
  break;
1644
1633
  case 'runview-runquery-result-direct-usage':
1645
- suggestions.push({
1646
- violation: violation.rule,
1647
- suggestion: 'RunView and RunQuery return result objects, not arrays. Access the data with .Results property.',
1634
+ violation.suggestion = {
1635
+ text: 'RunView and RunQuery return result objects, not arrays. Access the data with .Results property.',
1648
1636
  example: `// ❌ WRONG - Using result directly as array:
1649
1637
  const result = await utilities.rv.RunView({
1650
1638
  EntityName: 'Users',
@@ -1690,12 +1678,11 @@ setData(queryResult.Results || []); // NOT queryResult directly!
1690
1678
  // TotalRowCount?: number,
1691
1679
  // ExecutionTime?: number
1692
1680
  // }`
1693
- });
1681
+ };
1694
1682
  break;
1695
1683
  case 'styles-invalid-path':
1696
- suggestions.push({
1697
- violation: violation.rule,
1698
- suggestion: 'Fix invalid styles property paths. Use the correct ComponentStyles interface structure.',
1684
+ violation.suggestion = {
1685
+ text: 'Fix invalid styles property paths. Use the correct ComponentStyles interface structure.',
1699
1686
  example: `// ❌ WRONG - Invalid property paths:
1700
1687
  styles.fontSize.small // fontSize is not at root level
1701
1688
  styles.colors.background // colors.background exists
@@ -1710,12 +1697,11 @@ styles.spacing.sm // correct size name
1710
1697
  styles?.typography?.fontSize?.sm || '14px'
1711
1698
  styles?.colors?.background || '#FFFFFF'
1712
1699
  styles?.spacing?.sm || '8px'`
1713
- });
1700
+ };
1714
1701
  break;
1715
1702
  case 'styles-unsafe-access':
1716
- suggestions.push({
1717
- violation: violation.rule,
1718
- suggestion: 'Use optional chaining for nested styles access to prevent runtime errors.',
1703
+ violation.suggestion = {
1704
+ text: 'Use optional chaining for nested styles access to prevent runtime errors.',
1719
1705
  example: `// ❌ UNSAFE - Direct nested access:
1720
1706
  const fontSize = styles.typography.fontSize.md;
1721
1707
  const borderRadius = styles.borders.radius.sm;
@@ -1730,45 +1716,39 @@ const {
1730
1716
  fontSize: { md: fontSize = '14px' } = {}
1731
1717
  } = {}
1732
1718
  } = styles || {};`
1733
- });
1719
+ };
1734
1720
  break;
1735
1721
  }
1736
1722
  }
1737
- return suggestions;
1723
+ return violations;
1738
1724
  }
1739
1725
  static generateSyntaxErrorSuggestions(violations) {
1740
- const suggestions = [];
1741
1726
  for (const violation of violations) {
1742
1727
  if (violation.message.includes('Unterminated string')) {
1743
- suggestions.push({
1744
- violation: violation.rule,
1745
- suggestion: 'Check that all string literals are properly closed with matching quotes',
1728
+ violation.suggestion = {
1729
+ text: 'Check that all string literals are properly closed with matching quotes',
1746
1730
  example: 'Template literals with interpolation must use backticks: `text ${variable} text`'
1747
- });
1731
+ };
1748
1732
  }
1749
1733
  else if (violation.message.includes('Unexpected token') || violation.message.includes('export')) {
1750
- suggestions.push({
1751
- violation: violation.rule,
1752
- suggestion: 'Ensure all code is within the component function body',
1734
+ violation.suggestion = {
1735
+ text: 'Ensure all code is within the component function body',
1753
1736
  example: 'Remove any export statements or code outside the function definition'
1754
- });
1737
+ };
1755
1738
  }
1756
1739
  else if (violation.message.includes('import') && violation.message.includes('top level')) {
1757
- suggestions.push({
1758
- violation: violation.rule,
1759
- suggestion: 'Import statements are not allowed in components - use props instead',
1740
+ violation.suggestion = {
1741
+ text: 'Import statements are not allowed in components - use props instead',
1760
1742
  example: 'Access libraries through props: const { React, MaterialUI } = props.components'
1761
- });
1743
+ };
1762
1744
  }
1763
1745
  else {
1764
- suggestions.push({
1765
- violation: violation.rule,
1766
- suggestion: 'Fix the syntax error before the component can be compiled',
1746
+ violation.suggestion = {
1747
+ text: 'Fix the syntax error before the component can be compiled',
1767
1748
  example: 'Review the code at the specified line and column for syntax issues'
1768
- });
1749
+ };
1769
1750
  }
1770
1751
  }
1771
- return suggestions;
1772
1752
  }
1773
1753
  /**
1774
1754
  * Apply library-specific lint rules based on ComponentLibrary LintRules field
@@ -1786,50 +1766,78 @@ const {
1786
1766
  const libraryViolations = [];
1787
1767
  // Get the cached and compiled rules for this library
1788
1768
  const compiledRules = cache.getLibraryRules(lib.name);
1769
+ if (debugMode) {
1770
+ console.log(`\n 📚 Library: ${lib.name}`);
1771
+ if (compiledRules) {
1772
+ console.log(` ┌─ Has lint rules: ✅`);
1773
+ if (compiledRules.validators) {
1774
+ console.log(` ├─ Validators: ${Object.keys(compiledRules.validators).length}`);
1775
+ }
1776
+ if (compiledRules.initialization) {
1777
+ console.log(` ├─ Initialization rules: ✅`);
1778
+ }
1779
+ if (compiledRules.lifecycle) {
1780
+ console.log(` ├─ Lifecycle rules: ✅`);
1781
+ }
1782
+ console.log(` └─ Starting checks...`);
1783
+ }
1784
+ else {
1785
+ console.log(` └─ No lint rules defined`);
1786
+ }
1787
+ }
1789
1788
  if (compiledRules) {
1790
1789
  const library = compiledRules.library;
1791
1790
  const libraryName = library.Name || lib.name;
1792
1791
  // Apply initialization rules
1793
1792
  if (compiledRules.initialization) {
1793
+ if (debugMode) {
1794
+ console.log(` ├─ 🔍 Checking ${libraryName} initialization patterns...`);
1795
+ }
1794
1796
  const initViolations = this.checkLibraryInitialization(ast, libraryName, compiledRules.initialization);
1795
1797
  // Debug logging for library violations
1796
1798
  if (debugMode && initViolations.length > 0) {
1797
- console.log(`\n🔍 ${libraryName} Initialization Violations Found:`);
1799
+ console.log(` │ ⚠️ Found ${initViolations.length} initialization issue${initViolations.length > 1 ? 's' : ''}`);
1798
1800
  initViolations.forEach(v => {
1799
1801
  const icon = v.severity === 'critical' ? '🔴' :
1800
1802
  v.severity === 'high' ? '🟠' :
1801
1803
  v.severity === 'medium' ? '🟡' : '🟢';
1802
- console.log(` ${icon} [${v.severity}] Line ${v.line}: ${v.message}`);
1804
+ console.log(` ${icon} Line ${v.line}: ${v.message}`);
1803
1805
  });
1804
1806
  }
1805
1807
  libraryViolations.push(...initViolations);
1806
1808
  }
1807
1809
  // Apply lifecycle rules
1808
1810
  if (compiledRules.lifecycle) {
1811
+ if (debugMode) {
1812
+ console.log(` ├─ 🔄 Checking ${libraryName} lifecycle management...`);
1813
+ }
1809
1814
  const lifecycleViolations = this.checkLibraryLifecycle(ast, libraryName, compiledRules.lifecycle);
1810
1815
  // Debug logging for library violations
1811
1816
  if (debugMode && lifecycleViolations.length > 0) {
1812
- console.log(`\n🔍 ${libraryName} Lifecycle Violations Found:`);
1817
+ console.log(` │ ⚠️ Found ${lifecycleViolations.length} lifecycle issue${lifecycleViolations.length > 1 ? 's' : ''}`);
1813
1818
  lifecycleViolations.forEach(v => {
1814
1819
  const icon = v.severity === 'critical' ? '🔴' :
1815
1820
  v.severity === 'high' ? '🟠' :
1816
1821
  v.severity === 'medium' ? '🟡' : '🟢';
1817
- console.log(` ${icon} [${v.severity}] Line ${v.line}: ${v.message}`);
1822
+ console.log(` ${icon} Line ${v.line}: ${v.message}`);
1818
1823
  });
1819
1824
  }
1820
1825
  libraryViolations.push(...lifecycleViolations);
1821
1826
  }
1822
1827
  // Apply options validation
1823
1828
  if (compiledRules.options) {
1829
+ if (debugMode) {
1830
+ console.log(` ├─ ⚙️ Checking ${libraryName} configuration options...`);
1831
+ }
1824
1832
  const optionsViolations = this.checkLibraryOptions(ast, libraryName, compiledRules.options);
1825
1833
  // Debug logging for library violations
1826
1834
  if (debugMode && optionsViolations.length > 0) {
1827
- console.log(`\n🔍 ${libraryName} Options Violations Found:`);
1835
+ console.log(` │ ⚠️ Found ${optionsViolations.length} configuration issue${optionsViolations.length > 1 ? 's' : ''}`);
1828
1836
  optionsViolations.forEach(v => {
1829
1837
  const icon = v.severity === 'critical' ? '🔴' :
1830
1838
  v.severity === 'high' ? '🟠' :
1831
1839
  v.severity === 'medium' ? '🟡' : '🟢';
1832
- console.log(` ${icon} [${v.severity}] Line ${v.line}: ${v.message}`);
1840
+ console.log(` ${icon} Line ${v.line}: ${v.message}`);
1833
1841
  });
1834
1842
  }
1835
1843
  libraryViolations.push(...optionsViolations);
@@ -2129,6 +2137,13 @@ const {
2129
2137
  for (const [validatorName, validator] of Object.entries(validators)) {
2130
2138
  if (validator && validator.validateFn) {
2131
2139
  const beforeCount = context.violations.length;
2140
+ // Log that we're running this specific validator
2141
+ if (debugMode) {
2142
+ console.log(` ├─ 🔬 Running ${libraryName} validator: ${validatorName}`);
2143
+ if (validator.description) {
2144
+ console.log(` │ ℹ️ ${validator.description}`);
2145
+ }
2146
+ }
2132
2147
  // Traverse AST and apply validator
2133
2148
  (0, traverse_1.default)(ast, {
2134
2149
  enter(path) {
@@ -2139,27 +2154,31 @@ const {
2139
2154
  catch (error) {
2140
2155
  // Validator execution error - log but don't crash
2141
2156
  console.warn(`Validator ${validatorName} failed:`, error);
2157
+ if (debugMode) {
2158
+ console.error('Full error:', error);
2159
+ }
2142
2160
  }
2143
2161
  }
2144
2162
  });
2145
2163
  // Debug logging for this specific validator
2146
2164
  const newViolations = context.violations.length - beforeCount;
2147
2165
  if (debugMode && newViolations > 0) {
2148
- console.log(`\n📋 ${libraryName} - ${validatorName}:`);
2149
- console.log(` 📊 ${validator.description || 'No description'}`);
2150
- console.log(` ⚠️ Found ${newViolations} violation${newViolations > 1 ? 's' : ''}`);
2166
+ console.log(` │ ✓ Found ${newViolations} violation${newViolations > 1 ? 's' : ''}`);
2151
2167
  // Show the violations from this validator
2152
2168
  const validatorViolations = context.violations.slice(beforeCount);
2153
2169
  validatorViolations.forEach((v) => {
2154
2170
  const icon = v.type === 'error' || v.severity === 'critical' ? '🔴' :
2155
2171
  v.type === 'warning' || v.severity === 'high' ? '🟠' :
2156
2172
  v.severity === 'medium' ? '🟡' : '🟢';
2157
- console.log(` ${icon} Line ${v.line || 'unknown'}: ${v.message}`);
2173
+ console.log(`${icon} Line ${v.line || 'unknown'}: ${v.message}`);
2158
2174
  if (v.suggestion) {
2159
- console.log(` 💡 ${v.suggestion}`);
2175
+ console.log(`💡 ${v.suggestion}`);
2160
2176
  }
2161
2177
  });
2162
2178
  }
2179
+ else if (debugMode) {
2180
+ console.log(` │ ✓ No violations found`);
2181
+ }
2163
2182
  }
2164
2183
  }
2165
2184
  // Convert context violations to standard format
@@ -2513,7 +2532,7 @@ ComponentLinter.universalComponentRules = [
2513
2532
  severity: 'critical',
2514
2533
  line: path.node.loc?.start.line || 0,
2515
2534
  column: path.node.loc?.start.column || 0,
2516
- message: `Component '${varName}' shadows a dependency component. The component '${varName}' is already available from dependencies (auto-destructured), but this code is creating a new definition which overrides it.`,
2535
+ message: `Component '${varName}' shadows a dependency component. The component '${varName}' should be accessed via destructuring from components prop or as components.${varName}, but this code is creating a new definition which overrides it.`,
2517
2536
  code: `const ${varName} = ...`
2518
2537
  });
2519
2538
  }
@@ -2531,15 +2550,15 @@ ComponentLinter.universalComponentRules = [
2531
2550
  severity: 'critical',
2532
2551
  line: path.node.loc?.start.line || 0,
2533
2552
  column: path.node.loc?.start.column || 0,
2534
- message: `Component '${funcName}' shadows a dependency component. The component '${funcName}' is already available from dependencies (auto-destructured), but this code is creating a new function which overrides it.`,
2553
+ message: `Component '${funcName}' shadows a dependency component. The component '${funcName}' should be accessed via destructuring from components prop or as components.${funcName}, but this code is creating a new function which overrides it.`,
2535
2554
  code: `function ${funcName}(...)`
2536
2555
  });
2537
2556
  }
2538
2557
  }
2539
2558
  }
2540
2559
  });
2541
- // Components are now auto-destructured in the wrapper, so we don't need to check for manual destructuring
2542
- // We just need to check if they're being used directly
2560
+ // Components must be destructured from the components prop or accessed via components.ComponentName
2561
+ // Check if they're being used correctly
2543
2562
  let hasComponentsUsage = false;
2544
2563
  const usedDependencies = new Set();
2545
2564
  mainComponentPath.traverse({
@@ -2580,7 +2599,7 @@ ComponentLinter.universalComponentRules = [
2580
2599
  }
2581
2600
  }
2582
2601
  });
2583
- // Components are now auto-destructured, so just check for unused dependencies
2602
+ // Check for unused dependencies - components must be destructured or accessed via components prop
2584
2603
  if (dependencyNames.size > 0 && usedDependencies.size === 0) {
2585
2604
  const depList = Array.from(dependencyNames).join(', ');
2586
2605
  violations.push({
@@ -2588,7 +2607,7 @@ ComponentLinter.universalComponentRules = [
2588
2607
  severity: 'low',
2589
2608
  line: mainComponentPath.node.loc?.start.line || 0,
2590
2609
  column: mainComponentPath.node.loc?.start.column || 0,
2591
- message: `Component has dependencies [${depList}] defined in spec but they're not being used. These components are available for use.`,
2610
+ message: `Component has dependencies [${depList}] defined in spec but they're not being used. These components must be destructured from the components prop or accessed as components.ComponentName to use them.`,
2592
2611
  code: `// Available: ${depList}`
2593
2612
  });
2594
2613
  }
@@ -4954,10 +4973,13 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
4954
4973
  // Build set of allowed props: standard props + React special props + componentSpec properties
4955
4974
  const allowedProps = new Set([...standardProps, ...reactSpecialProps]);
4956
4975
  // Add props from componentSpec.properties if they exist
4976
+ // These are the architect-defined props that this component is allowed to accept
4977
+ const specDefinedProps = [];
4957
4978
  if (componentSpec?.properties) {
4958
4979
  for (const prop of componentSpec.properties) {
4959
4980
  if (prop.name) {
4960
4981
  allowedProps.add(prop.name);
4982
+ specDefinedProps.push(prop.name);
4961
4983
  }
4962
4984
  }
4963
4985
  }
@@ -4979,15 +5001,28 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
4979
5001
  }
4980
5002
  // Only report if there are non-allowed props
4981
5003
  if (invalidProps.length > 0) {
4982
- const customPropsMessage = componentSpec?.properties?.length
4983
- ? ` and custom props defined in spec: ${componentSpec.properties.map(p => p.name).join(', ')}`
4984
- : '';
5004
+ let message;
5005
+ if (specDefinedProps.length > 0) {
5006
+ message = `Component "${componentName}" accepts undeclared props: ${invalidProps.join(', ')}. ` +
5007
+ `This component can only accept: ` +
5008
+ `(1) Standard props: ${Array.from(standardProps).join(', ')}, ` +
5009
+ `(2) Spec-defined props: ${specDefinedProps.join(', ')}, ` +
5010
+ `(3) React props: ${Array.from(reactSpecialProps).join(', ')}. ` +
5011
+ `Any additional props must be defined in the component spec's properties array.`;
5012
+ }
5013
+ else {
5014
+ message = `Component "${componentName}" accepts undeclared props: ${invalidProps.join(', ')}. ` +
5015
+ `This component can only accept: ` +
5016
+ `(1) Standard props: ${Array.from(standardProps).join(', ')}, ` +
5017
+ `(2) React props: ${Array.from(reactSpecialProps).join(', ')}. ` +
5018
+ `To accept additional props, they must be defined in the component spec's properties array.`;
5019
+ }
4985
5020
  violations.push({
4986
5021
  rule: 'component-props-validation',
4987
5022
  severity: 'critical',
4988
5023
  line: path.node.loc?.start.line || 0,
4989
5024
  column: path.node.loc?.start.column || 0,
4990
- message: `Component "${componentName}" accepts undeclared props: ${invalidProps.join(', ')}. Components can only accept standard props: ${Array.from(standardProps).join(', ')}, React special props: ${Array.from(reactSpecialProps).join(', ')}${customPropsMessage}. All custom props must be defined in the component spec properties array.`
5025
+ message
4991
5026
  });
4992
5027
  }
4993
5028
  }
@@ -5012,15 +5047,28 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
5012
5047
  }
5013
5048
  }
5014
5049
  if (invalidProps.length > 0) {
5015
- const customPropsMessage = componentSpec?.properties?.length
5016
- ? ` and custom props defined in spec: ${componentSpec.properties.map(p => p.name).join(', ')}`
5017
- : '';
5050
+ let message;
5051
+ if (specDefinedProps.length > 0) {
5052
+ message = `Component "${componentName}" accepts undeclared props: ${invalidProps.join(', ')}. ` +
5053
+ `This component can only accept: ` +
5054
+ `(1) Standard props: ${Array.from(standardProps).join(', ')}, ` +
5055
+ `(2) Spec-defined props: ${specDefinedProps.join(', ')}, ` +
5056
+ `(3) React props: ${Array.from(reactSpecialProps).join(', ')}. ` +
5057
+ `Any additional props must be defined in the component spec's properties array.`;
5058
+ }
5059
+ else {
5060
+ message = `Component "${componentName}" accepts undeclared props: ${invalidProps.join(', ')}. ` +
5061
+ `This component can only accept: ` +
5062
+ `(1) Standard props: ${Array.from(standardProps).join(', ')}, ` +
5063
+ `(2) React props: ${Array.from(reactSpecialProps).join(', ')}. ` +
5064
+ `To accept additional props, they must be defined in the component spec's properties array.`;
5065
+ }
5018
5066
  violations.push({
5019
5067
  rule: 'component-props-validation',
5020
5068
  severity: 'critical',
5021
5069
  line: path.node.loc?.start.line || 0,
5022
5070
  column: path.node.loc?.start.column || 0,
5023
- message: `Component "${componentName}" accepts undeclared props: ${invalidProps.join(', ')}. Components can only accept standard props: ${Array.from(standardProps).join(', ')}, React special props: ${Array.from(reactSpecialProps).join(', ')}${customPropsMessage}. All custom props must be defined in the component spec properties array.`
5071
+ message
5024
5072
  });
5025
5073
  }
5026
5074
  }
@@ -5253,87 +5301,6 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
5253
5301
  return violations;
5254
5302
  }
5255
5303
  },
5256
- {
5257
- name: 'invalid-components-destructuring',
5258
- appliesTo: 'all',
5259
- test: (ast, componentName, componentSpec) => {
5260
- const violations = [];
5261
- // Build sets of valid component names and library names
5262
- const validComponentNames = new Set();
5263
- const libraryNames = new Set();
5264
- const libraryGlobalVars = new Set();
5265
- // Add dependency components
5266
- if (componentSpec?.dependencies) {
5267
- for (const dep of componentSpec.dependencies) {
5268
- if (dep.name) {
5269
- validComponentNames.add(dep.name);
5270
- }
5271
- }
5272
- }
5273
- // Add libraries
5274
- if (componentSpec?.libraries) {
5275
- for (const lib of componentSpec.libraries) {
5276
- if (lib.name) {
5277
- libraryNames.add(lib.name);
5278
- }
5279
- if (lib.globalVariable) {
5280
- libraryGlobalVars.add(lib.globalVariable);
5281
- }
5282
- }
5283
- }
5284
- // Check for manual destructuring from components (now optional since auto-destructuring is in place)
5285
- (0, traverse_1.default)(ast, {
5286
- VariableDeclarator(path) {
5287
- // Look for: const { Something } = components;
5288
- if (t.isObjectPattern(path.node.id) &&
5289
- t.isIdentifier(path.node.init) &&
5290
- path.node.init.name === 'components') {
5291
- for (const prop of path.node.id.properties) {
5292
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
5293
- const destructuredName = prop.key.name;
5294
- // Check if this is NOT a valid component from dependencies
5295
- if (!validComponentNames.has(destructuredName)) {
5296
- // Check if it might be a library being incorrectly destructured
5297
- if (libraryNames.has(destructuredName) || libraryGlobalVars.has(destructuredName)) {
5298
- violations.push({
5299
- rule: 'invalid-components-destructuring',
5300
- severity: 'critical',
5301
- line: prop.loc?.start.line || 0,
5302
- column: prop.loc?.start.column || 0,
5303
- message: `Attempting to destructure library "${destructuredName}" from components prop. Libraries should be accessed directly via their globalVariable, not from components.`,
5304
- code: `const { ${destructuredName} } = components;`
5305
- });
5306
- }
5307
- else {
5308
- violations.push({
5309
- rule: 'invalid-components-destructuring',
5310
- severity: 'high',
5311
- line: prop.loc?.start.line || 0,
5312
- column: prop.loc?.start.column || 0,
5313
- message: `Destructuring "${destructuredName}" from components prop, but it's not in the component's dependencies array. Either add it to dependencies or it might be a missing library.`,
5314
- code: `const { ${destructuredName} } = components;`
5315
- });
5316
- }
5317
- }
5318
- else {
5319
- // Valid component, but manual destructuring is now redundant
5320
- violations.push({
5321
- rule: 'invalid-components-destructuring',
5322
- severity: 'low',
5323
- line: prop.loc?.start.line || 0,
5324
- column: prop.loc?.start.column || 0,
5325
- message: `Manual destructuring of "${destructuredName}" from components prop is redundant. Components are now auto-destructured and available directly.`,
5326
- code: `const { ${destructuredName} } = components; // Can be removed`
5327
- });
5328
- }
5329
- }
5330
- }
5331
- }
5332
- }
5333
- });
5334
- return violations;
5335
- }
5336
- },
5337
5304
  {
5338
5305
  name: 'unsafe-array-operations',
5339
5306
  appliesTo: 'all',
@@ -5727,14 +5694,14 @@ Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxR
5727
5694
  });
5728
5695
  }
5729
5696
  } else if (componentsFromProp.has(tagName)) {
5730
- // This shouldn't happen since dependency components are auto-destructured
5731
- // But keep as a fallback check
5697
+ // Component is in dependencies but not destructured/accessible
5698
+ // This indicates the component wasn't properly destructured from components prop
5732
5699
  violations.push({
5733
5700
  rule: 'undefined-jsx-component',
5734
5701
  severity: 'high',
5735
5702
  line: openingElement.loc?.start.line || 0,
5736
5703
  column: openingElement.loc?.start.column || 0,
5737
- message: `JSX component "${tagName}" is in dependencies but appears to be undefined. There may be an issue with component registration.`,
5704
+ message: `JSX component "${tagName}" is in dependencies but appears to be undefined. Make sure to destructure it from the components prop: const { ${tagName} } = components;`,
5738
5705
  code: `<${tagName} ... />`
5739
5706
  });
5740
5707
  } else {
@@ -7543,87 +7510,117 @@ Correct pattern:
7543
7510
  appliesTo: 'all',
7544
7511
  test: (ast, componentName) => {
7545
7512
  const violations = [];
7546
- // Count all function declarations and expressions at the top level
7547
- const functionDeclarations = [];
7548
- const functionExpressions = [];
7549
- (0, traverse_1.default)(ast, {
7550
- FunctionDeclaration(path) {
7551
- // Only check top-level functions (not nested inside other functions)
7552
- const parent = path.getFunctionParent();
7553
- if (!parent) {
7554
- const funcName = path.node.id?.name || 'anonymous';
7555
- functionDeclarations.push({
7556
- name: funcName,
7557
- line: path.node.loc?.start.line || 0,
7558
- column: path.node.loc?.start.column || 0
7559
- });
7560
- }
7561
- },
7562
- VariableDeclaration(path) {
7563
- // Check for const/let/var func = function() or arrow functions at top level
7564
- const parent = path.getFunctionParent();
7565
- if (!parent) {
7566
- for (const declarator of path.node.declarations) {
7567
- if (t.isVariableDeclarator(declarator) &&
7568
- (t.isFunctionExpression(declarator.init) ||
7569
- t.isArrowFunctionExpression(declarator.init))) {
7570
- const funcName = t.isIdentifier(declarator.id) ? declarator.id.name : 'anonymous';
7571
- functionExpressions.push({
7572
- name: funcName,
7573
- line: declarator.loc?.start.line || 0,
7574
- column: declarator.loc?.start.column || 0
7575
- });
7576
- }
7577
- }
7578
- }
7579
- }
7580
- });
7581
- const allFunctions = [...functionDeclarations, ...functionExpressions];
7582
- // Check if we have more than one function
7583
- if (allFunctions.length > 1) {
7584
- // Find which one is the main component
7585
- const mainComponentIndex = allFunctions.findIndex(f => f.name === componentName);
7586
- const otherFunctions = allFunctions.filter((_, index) => index !== mainComponentIndex);
7513
+ // Check that the AST body contains exactly one statement and it's a function declaration
7514
+ const programBody = ast.program.body;
7515
+ // First, check if there's anything other than a single function declaration
7516
+ if (programBody.length === 0) {
7517
+ violations.push({
7518
+ rule: 'single-function-only',
7519
+ severity: 'critical',
7520
+ line: 1,
7521
+ column: 0,
7522
+ message: `Component code must contain exactly one function declaration named "${componentName}". No code found.`,
7523
+ code: `Add: function ${componentName}({ utilities, styles, components, callbacks, savedUserSettings, onSaveUserSettings }) { ... }`
7524
+ });
7525
+ return violations;
7526
+ }
7527
+ if (programBody.length > 1) {
7528
+ // Multiple top-level statements - not allowed
7587
7529
  violations.push({
7588
7530
  rule: 'single-function-only',
7589
7531
  severity: 'critical',
7590
- line: otherFunctions[0].line,
7591
- column: otherFunctions[0].column,
7592
- message: `Component code must contain ONLY the main component function "${componentName}". Found ${allFunctions.length} functions: ${allFunctions.map(f => f.name).join(', ')}. Move other functions to separate component dependencies.`,
7593
- code: `Remove functions: ${otherFunctions.map(f => f.name).join(', ')}`
7532
+ line: programBody[1].loc?.start.line || 0,
7533
+ column: programBody[1].loc?.start.column || 0,
7534
+ message: `Component code must contain ONLY a single function declaration. Found ${programBody.length} top-level statements. No code should exist before or after the function.`,
7535
+ code: `Remove all code except: function ${componentName}(...) { ... }`
7594
7536
  });
7595
- // Add a violation for each extra function
7596
- for (const func of otherFunctions) {
7537
+ // Report each extra statement
7538
+ for (let i = 1; i < programBody.length; i++) {
7539
+ const stmt = programBody[i];
7540
+ let stmtType = 'statement';
7541
+ if (t.isVariableDeclaration(stmt)) {
7542
+ stmtType = 'variable declaration';
7543
+ }
7544
+ else if (t.isFunctionDeclaration(stmt)) {
7545
+ stmtType = 'function declaration';
7546
+ }
7547
+ else if (t.isExpressionStatement(stmt)) {
7548
+ stmtType = 'expression';
7549
+ }
7597
7550
  violations.push({
7598
7551
  rule: 'single-function-only',
7599
7552
  severity: 'critical',
7600
- line: func.line,
7601
- column: func.column,
7602
- message: `Extra function "${func.name}" not allowed. Each component must be a single function. Move this to a separate component dependency.`,
7603
- code: `function ${func.name} should be a separate component`
7553
+ line: stmt.loc?.start.line || 0,
7554
+ column: stmt.loc?.start.column || 0,
7555
+ message: `Extra ${stmtType} not allowed. Only the component function should exist.`,
7556
+ code: ''
7604
7557
  });
7605
7558
  }
7606
7559
  }
7607
- // Also check that the single function matches the component name
7608
- if (allFunctions.length === 1 && allFunctions[0].name !== componentName) {
7560
+ // Check that the single statement is a function declaration (not arrow function or other)
7561
+ const firstStatement = programBody[0];
7562
+ if (!t.isFunctionDeclaration(firstStatement)) {
7563
+ let actualType = 'unknown statement';
7564
+ let suggestion = '';
7565
+ if (t.isVariableDeclaration(firstStatement)) {
7566
+ // Check if it's an arrow function or other variable
7567
+ const declarator = firstStatement.declarations[0];
7568
+ if (t.isVariableDeclarator(declarator)) {
7569
+ if (t.isArrowFunctionExpression(declarator.init) || t.isFunctionExpression(declarator.init)) {
7570
+ actualType = 'arrow function or function expression';
7571
+ suggestion = `Use function declaration syntax: function ${componentName}(...) { ... }`;
7572
+ }
7573
+ else {
7574
+ actualType = 'variable declaration';
7575
+ suggestion = 'Remove this variable and ensure only the component function exists';
7576
+ }
7577
+ }
7578
+ }
7579
+ else if (t.isExpressionStatement(firstStatement)) {
7580
+ actualType = 'expression statement';
7581
+ suggestion = 'Remove this expression and add the component function';
7582
+ }
7609
7583
  violations.push({
7610
7584
  rule: 'single-function-only',
7611
7585
  severity: 'critical',
7612
- line: allFunctions[0].line,
7613
- column: allFunctions[0].column,
7614
- message: `Component function name "${allFunctions[0].name}" does not match component name "${componentName}". The function must be named exactly as specified.`,
7615
- code: `Rename function to: function ${componentName}(...)`
7586
+ line: firstStatement.loc?.start.line || 0,
7587
+ column: firstStatement.loc?.start.column || 0,
7588
+ message: `Component must be a function declaration, not ${actualType}. ${suggestion}`,
7589
+ code: ''
7616
7590
  });
7591
+ // Don't check name if it's not a function declaration
7592
+ return violations;
7617
7593
  }
7618
- // Check for no function at all
7619
- if (allFunctions.length === 0) {
7594
+ // Check that the function name matches the component name
7595
+ const functionName = firstStatement.id?.name;
7596
+ if (functionName !== componentName) {
7620
7597
  violations.push({
7621
7598
  rule: 'single-function-only',
7622
7599
  severity: 'critical',
7623
- line: 1,
7624
- column: 0,
7625
- message: `Component code must contain exactly one function named "${componentName}". No functions found.`,
7626
- code: `Add: function ${componentName}({ utilities, styles, components, callbacks, savedUserSettings, onSaveUserSettings }) { ... }`
7600
+ line: firstStatement.loc?.start.line || 0,
7601
+ column: firstStatement.loc?.start.column || 0,
7602
+ message: `Component function name "${functionName}" does not match component name "${componentName}". The function must be named exactly as specified.`,
7603
+ code: `Rename to: function ${componentName}(...)`
7604
+ });
7605
+ }
7606
+ // Additional check: look for any code before the function that might have been missed
7607
+ // (e.g., leading variable declarations that destructure from React)
7608
+ if (programBody.length === 1 && t.isFunctionDeclaration(firstStatement)) {
7609
+ // Use traverse to find any problematic patterns inside
7610
+ (0, traverse_1.default)(ast, {
7611
+ Program(path) {
7612
+ // Check if there are any directives or other non-obvious code
7613
+ if (path.node.directives && path.node.directives.length > 0) {
7614
+ violations.push({
7615
+ rule: 'single-function-only',
7616
+ severity: 'high',
7617
+ line: 1,
7618
+ column: 0,
7619
+ message: 'Component should not have directives like "use strict". These are added automatically.',
7620
+ code: ''
7621
+ });
7622
+ }
7623
+ }
7627
7624
  });
7628
7625
  }
7629
7626
  return violations;
@@ -8061,6 +8058,44 @@ Correct pattern:
8061
8058
  });
8062
8059
  return violations;
8063
8060
  }
8061
+ },
8062
+ {
8063
+ name: 'no-react-destructuring',
8064
+ appliesTo: 'all',
8065
+ test: (ast, componentName, componentSpec) => {
8066
+ const violations = [];
8067
+ (0, traverse_1.default)(ast, {
8068
+ VariableDeclarator(path) {
8069
+ // Check for destructuring from React
8070
+ if (t.isObjectPattern(path.node.id) &&
8071
+ t.isIdentifier(path.node.init) &&
8072
+ path.node.init.name === 'React') {
8073
+ // Get the destructured properties
8074
+ const destructuredProps = path.node.id.properties
8075
+ .filter(prop => t.isObjectProperty(prop) && t.isIdentifier(prop.key))
8076
+ .map(prop => prop.key)
8077
+ .map(key => key.name);
8078
+ violations.push({
8079
+ rule: 'no-react-destructuring',
8080
+ severity: 'critical',
8081
+ line: path.node.loc?.start.line || 0,
8082
+ column: path.node.loc?.start.column || 0,
8083
+ message: `Cannot destructure from React. The hooks (${destructuredProps.join(', ')}) are already available as global functions in the React runtime.`,
8084
+ code: path.toString().substring(0, 100),
8085
+ suggestion: {
8086
+ text: `Remove the destructuring statement. React hooks like ${destructuredProps.join(', ')} are already available globally and don't need to be imported or destructured.`,
8087
+ example: `// Remove this line entirely:
8088
+ // const { ${destructuredProps.join(', ')} } = React;
8089
+
8090
+ // Just use the hooks directly:
8091
+ const [state, setState] = useState(initialValue);`
8092
+ }
8093
+ });
8094
+ }
8095
+ }
8096
+ });
8097
+ return violations;
8098
+ }
8064
8099
  }
8065
8100
  ];
8066
8101
  //# sourceMappingURL=component-linter.js.map