Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

103 lines
3.1 KiB
TypeScript

import type { SharedExpressionPathOption } from './context';
import type { ExpressionMode } from './modes';
import {
createValidationDiagnostic,
createValidationResult,
mergeValidationResults,
type SharedExpressionValidationResult,
} from './validation';
const TEMPLATE_TOKEN_PATTERN = /\{\{\s*([^{}]+?)\s*\}\}/g;
const EXPRESSION_PATH_PATTERN = /\b([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\])*)\b/g;
const normalizePath = (value: string): string => value.trim();
const compilePathPattern = (path: string): RegExp | null => {
if (!path.includes('*')) return null;
const escaped = path
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
.replace(/\\\*/g, '[^.\\[\\]]+');
return new RegExp(`^${escaped}$`);
};
const isKnownPath = (path: string, options: SharedExpressionPathOption[]): boolean => {
const normalizedPath = normalizePath(path);
if (!normalizedPath) return true;
for (const option of options) {
if (option.path === normalizedPath) return true;
const dynamicPattern = compilePathPattern(option.path);
if (dynamicPattern?.test(normalizedPath)) {
return true;
}
}
return false;
};
const createUnknownPathDiagnostic = (
path: string,
source: ExpressionMode
) =>
createValidationDiagnostic({
severity: 'info',
code: 'unknown-path',
source: `shared-path-validation:${source}`,
path,
message: `Unknown path "${path}" for current context.`,
});
export const extractTemplateTokenPaths = (template: string): string[] => {
const paths: string[] = [];
for (const match of template.matchAll(new RegExp(TEMPLATE_TOKEN_PATTERN))) {
const token = normalizePath(match[1] ?? '');
if (!token) continue;
paths.push(token);
}
return paths;
};
export const extractExpressionReferencePaths = (expression: string): string[] => {
const stripped = expression.replace(/(["'`])(?:\\.|(?!\1).)*\1/g, ' ');
const references = new Set<string>();
for (const match of stripped.matchAll(new RegExp(EXPRESSION_PATH_PATTERN))) {
const candidate = normalizePath(match[1] ?? '');
if (!candidate.includes('.')) continue;
references.add(candidate);
}
return [...references];
};
const validatePaths = (
paths: string[],
mode: ExpressionMode,
options: SharedExpressionPathOption[]
): SharedExpressionValidationResult => {
const diagnostics = paths
.filter((path) => !isKnownPath(path, options))
.map((path) => createUnknownPathDiagnostic(path, mode));
return createValidationResult(diagnostics);
};
export const validateSourcePaths = (params: {
source: string;
mode: ExpressionMode;
options: SharedExpressionPathOption[];
}): SharedExpressionValidationResult => {
const value = params.source ?? '';
if (!value.trim()) {
return createValidationResult([]);
}
if (params.mode === 'path-only') {
return validatePaths([value], params.mode, params.options);
}
if (params.mode === 'template') {
return validatePaths(extractTemplateTokenPaths(value), params.mode, params.options);
}
return mergeValidationResults(
validatePaths(extractExpressionReferencePaths(value), params.mode, params.options),
createValidationResult([])
);
};