# Component Linter Architecture Guide

This document describes the architecture of the MemberJunction interactive component linter. It is intended for developers who need to understand, maintain, or extend the linter.

## Overview

The component linter validates React interactive components generated by the Skip-Brain AI system. These components run inside a sandboxed runtime where they cannot import modules, export values, or access `window` — all dependencies are injected as props (`utilities`, `styles`, `components`, `callbacks`). The linter enforces these constraints and catches common mistakes before components are executed.

### Key Numbers
- **57 lint rules** in individual self-registering files
- **~1,400 line orchestrator** (`component-linter.ts`)
- **425 unit tests** across 8 test files
- **306 component fixtures** (161 broken, 96 fixed, 44 valid, 5 future)

## Architecture Diagram

```
┌─────────────────────────────────────────────────────────────────┐
│                  ComponentLinter.lintComponent()                 │
│                       (orchestrator)                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Parse ──► Babel AST                                         │
│  2. Type Inference ──► TypeContext (runs once)                   │
│     - useState type tracking                                    │
│     - Callback parameter propagation (.map, .filter, etc.)      │
│     - setState type updates                                     │
│     - RunView/RunQuery result type resolution                   │
│  3. Discover rules via ClassFactory (BaseLintRule registrations) │
│  4. Execute each rule's Test(ast, name, spec, options, ctx)     │
│  5. Run TypeCompatibilityRule (consolidated type rules)          │
│  6. Run ComponentPropRule (unified prop validation)              │
│  7. Validate data requirements against spec                     │
│  8. Apply library-specific lint rules (from database)           │
│  9. Deduplicate → return LintResult                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

## Directory Structure

```
packages/React/test-harness/
├── src/
│   ├── lib/
│   │   ├── component-linter.ts              # Orchestrator (~1,400 lines)
│   │   ├── lint-rule.ts                     # BaseLintRule abstract class
│   │   ├── lint-utils.ts                    # Shared utilities (createViolation, JSX helpers)
│   │   ├── runtime-rules/                   # 57 individual rule files
│   │   │   ├── index.ts                     # Barrel import for tree-shaking prevention
│   │   │   ├── runview-call-validation.ts   # RunView call-site validation
│   │   │   ├── runquery-call-validation.ts  # RunQuery call-site validation
│   │   │   ├── data-result-validation.ts    # RunView/RunQuery/Search result usage
│   │   │   ├── search-call-validation.ts    # Search API validation
│   │   │   └── ... (53 more)
│   │   ├── type-rules/
│   │   │   └── type-compatibility-rule.ts   # Consolidated type checking
│   │   ├── schema-validation/
│   │   │   ├── component-prop-rule.ts       # Unified prop validation
│   │   │   └── semantic-validators/         # Constraint-based validators
│   │   ├── type-inference-engine.ts         # Type inference (runs once before rules)
│   │   ├── type-context.ts                  # Type context shared across rules
│   │   ├── control-flow-analyzer.ts         # Control flow analysis
│   │   ├── styles-type-analyzer.ts          # Styles object type analysis
│   │   ├── prop-value-extractor.ts          # Static prop value extraction from AST
│   │   └── library-lint-cache.ts            # Caches compiled library-specific rules
│   ├── __tests__/
│   │   └── component-linter/
│   │       ├── fixtures.test.ts             # 1 test per fixture (~306 tests)
│   │       ├── basic-rules.test.ts          # Unit tests for specific rules
│   │       ├── child-delegation.test.ts     # Child component delegation tests
│   │       ├── skip-warnings.test.ts        # Skip-with-warning fallback tests
│   │       ├── runquery-params-variable.test.ts  # Variable parameter tests
│   │       ├── consolidation-audit.test.ts  # Consolidation regression tests
│   │       └── search-validation.test.ts    # Search utility tests
│   └── index.ts                             # Package exports
├── fixtures/
│   └── component-linter/
│       ├── broken-components/               # 161 fixtures (must produce violations)
│       ├── fixed-components/                # 96 fixtures (must not have invalid-property violations)
│       ├── valid-components/                # 44 fixtures (must produce zero violations)
│       └── future-components/               # 5 fixtures (unimplemented rules)
├── LINTER-ARCHITECTURE.md                   # This file
└── VISITOR-MERGING-PLAN.md                  # Future optimization plan
```

## How Rules Work

### BaseLintRule (Extensible via ClassFactory)

All rules extend `BaseLintRule` and register via MJGlobal's `@RegisterClass` decorator. This makes the linter extensible — external packages (e.g., Skip-Brain) can define custom rules that the linter discovers automatically.

```typescript
import { RegisterClass } from '@memberjunction/global';
import { BaseLintRule } from '@memberjunction/react-test-harness';

@RegisterClass(BaseLintRule, 'my-custom-rule')
export class MyCustomRule extends BaseLintRule {
  get Name() { return 'my-custom-rule'; }
  get AppliesTo(): 'all' | 'child' | 'root' { return 'all'; }

  Test(ast, componentName, componentSpec, options, typeContext) {
    const violations = [];
    // ... validation logic using Babel AST traversal
    return violations;
  }
}
```

The orchestrator discovers rules via:
```typescript
MJGlobal.Instance.ClassFactory.GetAllRegistrations(BaseLintRule)
```

### Rule Scope (`AppliesTo`)

- **`all`**: Applies to both root and child components. Most rules use this.
- **`root`**: Only the top-level component. Used for `required-queries-not-called`.
- **`child`**: Only embedded child components. Used for `no-child-implementation`.

### Violation Interface

```typescript
interface Violation {
  rule: string;           // Rule name
  severity: 'critical' | 'high' | 'medium' | 'low';
  line: number;
  column: number;
  message: string;
  code?: string;          // Code snippet
  suggestion?: { text: string; example?: string; };
}
```

### Severity Guidelines

| Severity | When to use | Example |
|----------|-------------|---------|
| `critical` | Breaks the runtime or causes crashes | Import/export statements, IIFE wrappers |
| `high` | Likely runtime error or incorrect behavior | Wrong RunView property names, invalid entity fields |
| `medium` | Code quality issue, potential bug | Case mismatches, missing null guards, optional API access |
| `low` | Informational warnings | Missing metadata for validation |

## How to Add a New Rule

### 1. Create the rule file

Create `runtime-rules/my-new-rule.ts`:

```typescript
import traverse, { NodePath } from '@babel/traverse';
import * as t from '@babel/types';
import { RegisterClass } from '@memberjunction/global';
import { BaseLintRule } from '../lint-rule';
import { Violation } from '../component-linter';

@RegisterClass(BaseLintRule, 'my-new-rule')
export class MyNewRule extends BaseLintRule {
  get Name() { return 'my-new-rule'; }
  get AppliesTo(): 'all' | 'child' | 'root' { return 'all'; }

  Test(ast: t.File, componentName: string, componentSpec?: ComponentSpec): Violation[] {
    const violations: Violation[] = [];

    traverse(ast, {
      CallExpression(path: NodePath<t.CallExpression>) {
        // Your validation logic
      },
    });

    return violations;
  }
}
```

### 2. Add the import to index.ts

```typescript
import './my-new-rule';
```

### 3. Add test fixtures

- **Broken fixture** (`fixtures/component-linter/broken-components/`): Component with the bug. Test asserts ≥1 violation.
- **Valid fixture** (`fixtures/component-linter/valid-components/`): Correct component. Test asserts 0 violations.

### 4. Run tests

```bash
cd packages/React/test-harness
npm run build
npx vitest run
```

### External Rules (e.g., from Skip-Brain)

External packages can define rules by:
1. Extending `BaseLintRule` from `@memberjunction/react-test-harness`
2. Adding `@RegisterClass(BaseLintRule, 'rule-name')` decorator
3. Ensuring the manifest system includes the rule file

The linter discovers all registered rules via ClassFactory at runtime.

## Rule Categories

### Runtime Constraint Rules
Enforce the sandboxed runtime environment:
`no-import-statements`, `no-export-statements`, `no-require-statements`, `no-iife-wrapper`, `no-return-component`, `no-window-access`, `use-function-declaration`, `single-function-only`

### Data Access Rules (Consolidated)
Three focused rules covering all RunView/RunQuery/Search validation:
- **`runview-call-validation`**: Property names/types, EntityName exists, argument structure
- **`runquery-call-validation`**: Property names/types, Parameters format/names/types/nulls, CategoryPath, SQL injection, variable resolution via TypeContext
- **`data-result-validation`**: Invalid property access, spread, array methods, Success guards, setState patterns — for RunView, RunQuery, AND Search results

### Search API Rules
- **`search-availability-check`**: Guards `utilities.search` access (optional property)
- **`search-call-validation`**: Search() params (Query required, Filters, SourceTypes enum) and PreviewSearch() params

### Entity/Query Field Rules
- **`entity-field-access-validation`**: Validates field access on RunView entity results (typos, case, nonexistent, type coercion)
- **`query-result-field-access-validation`**: Same for RunQuery results
- **`chart-field-validation`**: Chart prop field references against data source
- **`datagrid-field-validation`**: Grid column field references

### Component Structure Rules
`react-component-naming`, `component-name-mismatch`, `pass-standard-props`, `no-react-destructuring`, `no-data-prop`, `no-child-implementation`, `component-props-validation`

### Component Dependency Rules
`component-not-in-dependencies`, `undefined-component-usage`, `unused-libraries`, `unused-component-dependencies`, `library-variable-names`, `dependency-shadowing`, `validate-component-references`, `component-usage-without-destructuring`, `child-component-prop-validation`

### Callback & Event Rules (Consolidated)
- **`callback-event-validation`**: Callback method usage, parameter signatures, passthrough validation, event invocation null-checks

### Best Practice Rules
`prefer-async-await`, `prefer-jsx-syntax`, `react-hooks-rules`, `useeffect-unstable-dependencies`, `unsafe-array-operations`, `unsafe-formatting-methods`, `string-replace-all-occurrences`, `string-template-validation`

### Style Rules (Consolidated)
- **`styles-validation`**: Invalid path access, unsafe access patterns

### Type & Inference Rules
`type-inference-errors`, `type-mismatch-operation`

### Utility Rules
`utilities-api-validation`, `utilities-no-direct-instantiation`, `ai-tools-availability-check`

### State & Settings Rules
`saved-user-settings-pattern`, `noisy-settings-updates`, `prop-state-sync`, `property-name-consistency`, `server-reload-on-client-operation`

## Type Inference Engine

The `TypeInferenceEngine` runs once before all rules and builds a `TypeContext` — a map from every variable/expression to its inferred type. Rules query this context instead of doing their own type analysis.

### What It Tracks
- **useState**: `const [data, setData] = useState([])` → `data` typed as array
- **setState propagation**: `setData(result.Results)` → updates `data`'s type
- **RunView/RunQuery results**: Entity/query name preserved through `.Results` access
- **Callback parameters**: `.map(m => m.Salary)` → `m` inherits entity-row type from array
- **Object literals**: Field names and types tracked through initializers
- **Destructuring**: Types propagated through object and array destructuring

### How Rules Use It
```typescript
Test(ast, componentName, componentSpec, options, typeContext) {
  // Look up a variable's inferred type
  const varType = typeContext?.getVariableType('params');
  if (varType?.type === 'object' && varType.fields) {
    // Validate fields against expected schema
  }
}
```

## Metadata Fallback Strategy

Rules that need entity, query, or component metadata follow a 3-tier fallback:

1. **Primary**: Use metadata from `componentSpec` (properties, dataRequirements, dependencies)
2. **Fallback 1**: Load from database via `Metadata` or `ComponentMetadataEngine` (requires DB connection)
3. **Fallback 2**: Skip validation with a `low` severity warning (no false positives)

## Test Fixtures

| Directory | Count | Assertion |
|-----------|-------|-----------|
| `broken-components/` | 161 | Must produce ≥1 violation |
| `fixed-components/` | 96 | Must NOT produce invalid-property violations |
| `valid-components/` | 44 | Must produce 0 actionable violations (low OK) |
| `future-components/` | 5 | Not executed (unimplemented rules) |

Each fixture runs as its own vitest test case for clear regression debugging.

### Running Tests With Database

Create `.env` in `packages/React/test-harness/`:

```
DB_HOST=your-host
DB_DATABASE=your-db
DB_USERNAME=your-user
DB_PASSWORD=your-password
DB_PORT=1433
DB_TRUST_SERVER_CERTIFICATE=1
```

The vitest setup file auto-connects and initializes the MJ metadata provider.

### Running Tests

```bash
cd packages/React/test-harness
npm run build        # Build first
npx vitest run       # Run all tests
npx vitest run --reporter=verbose  # See individual fixture results
```

## SQL Dialect Configuration

The linter's SQL WHERE clause validation supports multiple database platforms. Pass an `SQLParserDialect` to `lintComponent()`:

```typescript
import { GetDialect } from '@memberjunction/sql-dialect';

const result = await ComponentLinter.lintComponent(
  code, name, spec, true, contextUser, false, options,
  GetDialect('postgresql')  // or 'sqlserver' (default)
);
```

## Future Work

- **Visitor merging**: Collapse ~57 separate AST traversals into 1 merged traversal for 3-10x speedup. See [VISITOR-MERGING-PLAN.md](VISITOR-MERGING-PLAN.md).
- **5 remaining future fixtures**: Spread field conflicts, chart empty fields, entity array sort, cross-component aggregation/transform.
