@happyvertical/smrt-scanner 0.40.69 → 0.41.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.
- package/AGENTS.md +26 -2
- package/dist/chunks/{scanner-LeMvLJ6X.js → scanner-jUIN438d.js} +16 -4
- package/dist/chunks/{scanner-LeMvLJ6X.js.map → scanner-jUIN438d.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +70 -0
- package/dist/index.js +137 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -18,6 +18,18 @@ executes the source.
|
|
|
18
18
|
- `parseFile` / `parseSource` — parse a single file or a source string to a
|
|
19
19
|
`FileScanResult` (classes, errors, type aliases, SMRT imports).
|
|
20
20
|
- `extractSmrtImports` — pull SMRT-related imports from a parsed file.
|
|
21
|
+
- `lintNumericPrecision(classes, sourceText?)` — flags persisted `number` fields
|
|
22
|
+
whose declared precision contradicts their name (#2361), returning a `kind`
|
|
23
|
+
per finding. **Money is exact** and stored as integer minor units, so
|
|
24
|
+
`subtotal = 0.0` is a `money` finding; **rates are fractional**, so
|
|
25
|
+
`taxRate = 0` is a `rate` finding. `classifyNumericFieldName` does head-noun
|
|
26
|
+
matching and lets a rate word win outright, so `taxRate` is a rate even though
|
|
27
|
+
`tax` is money, while `amountCents` and `totalTokensUsed` classify as neither.
|
|
28
|
+
`weight`/`score`/`factor`/`percent` are deliberately unclassified — they are
|
|
29
|
+
commonly whole numbers. Explicit `@field({ type })`, `@meta`/`Meta<T>`,
|
|
30
|
+
transient, relationship, and static fields are exempt.
|
|
31
|
+
`sourceMayContainNumericPrecisionIssue(source)` is the cheap pre-filter
|
|
32
|
+
callers use to avoid parsing every file; `dev:knowledge-check` drives both.
|
|
21
33
|
- `verifyManifestCompleteness({ packageDir })` — publish guard: re-scans `src/`
|
|
22
34
|
and asserts every `@smrt()` object reached `dist/manifest.json` (issue #1483).
|
|
23
35
|
Returns `ok` / `incomplete` / `missing-manifest` / `scan-error` / `skipped`.
|
|
@@ -89,9 +101,13 @@ exhausts the heap when the scanner is pointed at an application root (#2275):
|
|
|
89
101
|
- **`ManifestBuilder` / `discoverBaseClasses` live in `@happyvertical/smrt-core`,
|
|
90
102
|
not here.** This package is the lower-level AST layer; core orchestrates
|
|
91
103
|
manifest generation and base-class discovery on top of it.
|
|
92
|
-
- **0 vs 0.0 heuristic**: `count = 0` → integer, `
|
|
104
|
+
- **0 vs 0.0 heuristic**: `count = 0` → integer, `ratio = 0.0` → decimal; the
|
|
93
105
|
raw initializer text (not the parsed value) decides, and negative defaults are
|
|
94
|
-
unwrapped from their `UnaryExpression` before the check.
|
|
106
|
+
unwrapped from their `UnaryExpression` before the check. The rule is silent
|
|
107
|
+
and SQLite's affinity masks the consequence, so money- and rate-shaped fields
|
|
108
|
+
are gated by `lintNumericPrecision` rather than left to review (#2361).
|
|
109
|
+
- **`RawFieldDefinition.line` is `0`**: the OXC AST nodes reaching this package
|
|
110
|
+
carry no `loc`. Pass the source text to a consumer that needs a real line.
|
|
95
111
|
- **Static property capture**: captures `uiSlots` and `adminRoutes` for agent
|
|
96
112
|
manifest generation.
|
|
97
113
|
- **`@smrt()` config spreads resolve only against unescaped same-file `const`s** (issue
|
|
@@ -112,3 +128,11 @@ exhausts the heap when the scanner is pointed at an application root (#2275):
|
|
|
112
128
|
but its taint replays into the diagnostics of every decorator that spreads
|
|
113
129
|
it, transitively through constant chains. Without that the silent drop simply
|
|
114
130
|
moves one level up. An unused tainted constant reports nothing.
|
|
131
|
+
- **Relationship targets are resolved, not copied**: `@foreignKey`,
|
|
132
|
+
`@oneToMany` and `@manyToMany` arguments arrive as raw source text.
|
|
133
|
+
`'Target'`/`Target` pass through and a forward-reference thunk
|
|
134
|
+
(`() => Target`, including the dotted `() => Target.column` form) is unwrapped
|
|
135
|
+
to its target, matching the runtime decorator, which resolves the thunk by
|
|
136
|
+
invoking it. Any other expression (a call, a computed reference) yields
|
|
137
|
+
`related: undefined` — writing the raw source through produced a garbage FK
|
|
138
|
+
table name and, once FK columns are indexed, a garbage index (#2379).
|
|
@@ -1261,6 +1261,18 @@ function stripQuotes(value) {
|
|
|
1261
1261
|
const match = value.match(/^(['"`])(.+)\1$/);
|
|
1262
1262
|
return match ? match[2] : value;
|
|
1263
1263
|
}
|
|
1264
|
+
var QUOTED_LITERAL_PATTERN = /^(['"`])(.*)\1$/s;
|
|
1265
|
+
var RELATED_TARGET_PATTERN = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/;
|
|
1266
|
+
var RELATED_THUNK_PATTERN = /^\(\s*\)\s*=>\s*(?:([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)|\{\s*return\s+([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*;?\s*\})$/;
|
|
1267
|
+
function resolveRelatedArgument(raw) {
|
|
1268
|
+
const value = raw?.trim();
|
|
1269
|
+
if (!value) return void 0;
|
|
1270
|
+
const quoted = value.match(QUOTED_LITERAL_PATTERN);
|
|
1271
|
+
if (quoted) return quoted[2].trim() || void 0;
|
|
1272
|
+
if (RELATED_TARGET_PATTERN.test(value)) return value;
|
|
1273
|
+
const thunk = value.match(RELATED_THUNK_PATTERN);
|
|
1274
|
+
return thunk ? thunk[1] ?? thunk[2] : void 0;
|
|
1275
|
+
}
|
|
1264
1276
|
function createQualifiedName(packageName, className) {
|
|
1265
1277
|
return `${packageName}:${className}`;
|
|
1266
1278
|
}
|
|
@@ -1569,7 +1581,7 @@ var ManifestAdapter = class ManifestAdapter {
|
|
|
1569
1581
|
};
|
|
1570
1582
|
}
|
|
1571
1583
|
if (decorator.name === "foreignKey") {
|
|
1572
|
-
const relatedClass =
|
|
1584
|
+
const relatedClass = resolveRelatedArgument(decorator.arguments[0]);
|
|
1573
1585
|
const parsedOptions = this.parseFieldDecoratorOptions(decorator.arguments[1]);
|
|
1574
1586
|
const meta = {};
|
|
1575
1587
|
const META_KEYS = [
|
|
@@ -1640,7 +1652,7 @@ var ManifestAdapter = class ManifestAdapter {
|
|
|
1640
1652
|
};
|
|
1641
1653
|
}
|
|
1642
1654
|
if (decorator.name === "oneToMany") {
|
|
1643
|
-
const relatedClass =
|
|
1655
|
+
const relatedClass = resolveRelatedArgument(decorator.arguments[0]);
|
|
1644
1656
|
const parsedOptions = this.parseFieldDecoratorOptions(decorator.arguments[1]);
|
|
1645
1657
|
const meta = {};
|
|
1646
1658
|
if (parsedOptions?.foreignKey !== void 0) meta.foreignKey = parsedOptions.foreignKey;
|
|
@@ -1653,7 +1665,7 @@ var ManifestAdapter = class ManifestAdapter {
|
|
|
1653
1665
|
};
|
|
1654
1666
|
}
|
|
1655
1667
|
if (decorator.name === "manyToMany") {
|
|
1656
|
-
const relatedClass =
|
|
1668
|
+
const relatedClass = resolveRelatedArgument(decorator.arguments[0]);
|
|
1657
1669
|
const parsedOptions = this.parseFieldDecoratorOptions(decorator.arguments[1]);
|
|
1658
1670
|
const meta = {};
|
|
1659
1671
|
if (parsedOptions?.through !== void 0) meta.through = parsedOptions.through;
|
|
@@ -2168,4 +2180,4 @@ var OxcScanner = class {
|
|
|
2168
2180
|
//#endregion
|
|
2169
2181
|
export { parseSource as a, normalizeGlobSeparators as c, parseFile as i, relativeGlobToCwd as l, ManifestAdapter as n, InheritanceResolver as o, extractSmrtImports as r, discoverSourceFiles as s, OxcScanner as t };
|
|
2170
2182
|
|
|
2171
|
-
//# sourceMappingURL=scanner-
|
|
2183
|
+
//# sourceMappingURL=scanner-jUIN438d.js.map
|