chore(template): clean-slate template snapshot from bb4a0c7
Curated, product-agnostic snapshot of the post-story-04 tree: demo content deleted, auth-only reference feature, web-next shell, all gates green. Product-specific docs, ADRs 027-029, PRDs/epics/archive, editor library traces, and product naming are curated out; generic template repairs (coverage provider devDeps, root test:coverage script, live lint fixes, root-only release-please) are kept. See TEMPLATE.md for provenance, curation list, and usage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
99
packages/core-eslint/rules/_event-ast.js
Normal file
99
packages/core-eslint/rules/_event-ast.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parse } from "@typescript-eslint/parser";
|
||||
|
||||
/** Unwrap `as`/`satisfies` expressions, return a string literal's value. */
|
||||
function stringFromNode(node) {
|
||||
if (!node) return null;
|
||||
if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") {
|
||||
return stringFromNode(node.expression);
|
||||
}
|
||||
if (node.type === "Literal" && typeof node.value === "string") {
|
||||
return node.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the event `name` from an event-descriptor initializer. Handles the
|
||||
* two shapes the codebase and `pnpm turbo gen event` produce:
|
||||
* - `defineEvent("feature.event", schema)` — the core-events helper
|
||||
* - `{ name: "feature.event" as const, schema }` — the inline descriptor
|
||||
* (used when core-events is not installed)
|
||||
*/
|
||||
function nameFromInit(init) {
|
||||
if (!init) return null;
|
||||
if (init.type === "TSAsExpression" || init.type === "TSSatisfiesExpression") {
|
||||
return nameFromInit(init.expression);
|
||||
}
|
||||
if (
|
||||
init.type === "CallExpression" &&
|
||||
init.callee.type === "Identifier" &&
|
||||
init.callee.name === "defineEvent" &&
|
||||
init.arguments.length > 0
|
||||
) {
|
||||
return stringFromNode(init.arguments[0]);
|
||||
}
|
||||
if (init.type === "ObjectExpression") {
|
||||
const nameProp = init.properties.find(
|
||||
(p) =>
|
||||
p.type === "Property" &&
|
||||
p.key.type === "Identifier" &&
|
||||
p.key.name === "name",
|
||||
);
|
||||
return nameProp ? stringFromNode(nameProp.value) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a relative import source (evaluated from `fromFile`) to a `.ts`
|
||||
* file on disk. Returns null for bare/package specifiers or paths that don't
|
||||
* resolve — callers treat null as "can't analyse, skip".
|
||||
*/
|
||||
export function resolveRelativeImport(source, fromFile) {
|
||||
if (typeof source !== "string" || !source.startsWith(".")) return null;
|
||||
const base = path.resolve(path.dirname(fromFile), source);
|
||||
for (const candidate of [`${base}.ts`, path.join(base, "index.ts")]) {
|
||||
if (fs.existsSync(candidate)) return candidate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an event-contract file and return the `name` of the event descriptor
|
||||
* exported under `exportName`. Returns null when the file can't be read or
|
||||
* parsed, or doesn't export a recognised descriptor under that name.
|
||||
*/
|
||||
export function eventNameFromFile(filePath, exportName) {
|
||||
let source;
|
||||
try {
|
||||
source = fs.readFileSync(filePath, "utf8");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
let ast;
|
||||
try {
|
||||
ast = parse(source, { loc: false, range: false });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
for (const stmt of ast.body) {
|
||||
let varDecl = null;
|
||||
if (
|
||||
stmt.type === "ExportNamedDeclaration" &&
|
||||
stmt.declaration?.type === "VariableDeclaration"
|
||||
) {
|
||||
varDecl = stmt.declaration;
|
||||
} else if (stmt.type === "VariableDeclaration") {
|
||||
varDecl = stmt;
|
||||
}
|
||||
if (!varDecl) continue;
|
||||
for (const d of varDecl.declarations) {
|
||||
if (d.id.type === "Identifier" && d.id.name === exportName) {
|
||||
return nameFromInit(d.init);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user