@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.9 → 3.9.3-ultramodern.1
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/bin/modern-api-check-files.mjs +4 -1
- package/bin/modern-api-check.mjs +1 -1
- package/bin/modern-i18n-check.mjs +4 -0
- package/dist/cjs/cli/microvertical-api-check.cjs +36 -7
- package/dist/cjs/cli/workspace-source-check.cjs +90 -17
- package/dist/cjs/microvertical-api-baseline.cjs +81 -244
- package/dist/cjs/microvertical-api-boundary.cjs +116 -38
- package/dist/cjs/microvertical-api-owner.cjs +5 -2
- package/dist/cjs/module-graph.cjs +1820 -0
- package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +1 -1
- package/dist/cjs/source-analysis.cjs +108 -0
- package/dist/cjs/strict-effect-runtime.cjs +16 -51
- package/dist/esm/cli/microvertical-api-check.js +26 -7
- package/dist/esm/cli/workspace-source-check.js +86 -13
- package/dist/esm/microvertical-api-baseline.js +66 -214
- package/dist/esm/microvertical-api-boundary.js +114 -39
- package/dist/esm/microvertical-api-owner.js +5 -2
- package/dist/esm/module-graph.js +1762 -0
- package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm/source-analysis.js +48 -0
- package/dist/esm/strict-effect-runtime.js +16 -50
- package/dist/esm-node/cli/microvertical-api-check.js +26 -7
- package/dist/esm-node/cli/workspace-source-check.js +86 -13
- package/dist/esm-node/microvertical-api-baseline.js +66 -214
- package/dist/esm-node/microvertical-api-boundary.js +114 -39
- package/dist/esm-node/microvertical-api-owner.js +5 -2
- package/dist/esm-node/module-graph.js +1763 -0
- package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm-node/source-analysis.js +49 -0
- package/dist/esm-node/strict-effect-runtime.js +16 -50
- package/dist/types/cli/microvertical-api-check.d.ts +1 -1
- package/dist/types/cli/workspace-source-check.d.ts +1 -1
- package/dist/types/microvertical-api-baseline.d.ts +0 -5
- package/dist/types/microvertical-api-boundary.d.ts +15 -1
- package/dist/types/microvertical-api-owner.d.ts +1 -1
- package/dist/types/module-graph.d.ts +92 -0
- package/dist/types/source-analysis.d.ts +13 -0
- package/package.json +17 -13
|
@@ -21,7 +21,7 @@ const createStrictEffectApiBoundariesRule = ()=>({
|
|
|
21
21
|
});
|
|
22
22
|
reportProgramPattern(context, node, source, /@modern-js\/plugin-bff\/(?:server|hono-server)(?![\w-])/u, 'UltraModern API workspaces must not import Hono server helpers; use @modern-js/bff-effect/effect-edge and HttpApi.');
|
|
23
23
|
reportProgramPattern(context, node, source, /\bruntimeFramework\s*(?::|=)\s*['"]hono['"]/u, 'UltraModern API apps must use bff.runtimeFramework: "effect".');
|
|
24
|
-
reportProgramPattern(context, node, source, /(?:from|import)\s*['"][^'"]*(?:api\/effect|shared\/effect)[^'"]*['"]|shared-effect-api/u, 'Import API code from direct api/index.ts, shared/api.ts or src/api/* paths, not api/effect, shared/effect or shared Effect API packages.');
|
|
24
|
+
reportProgramPattern(context, node, source, /(?:from|import)\s*['"][^'"]*(?:api\/effect|shared\/effect)(?=\/|\.[cm]?[jt]sx?['"]|['"])[^'"]*['"]|shared-effect-api/u, 'Import API code from direct api/index.ts, shared/api.ts or src/api/* paths, not api/effect, shared/effect or shared Effect API packages.');
|
|
25
25
|
reportProgramPattern(context, node, source, /\bstrictEffectApproach\s*(?::|=)\s*false\b/u, 'UltraModern API apps must keep strictEffectApproach enabled.');
|
|
26
26
|
if (isApiEntryFile(filename)) {
|
|
27
27
|
const violation = strictEffectRuntimeTopologyViolation(source, createEffectApiImportResolver(filename));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { parse } from "@babel/parser";
|
|
3
|
+
import traverse, { Hub, NodePath } from "@babel/traverse";
|
|
4
|
+
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
5
|
+
const consumerParserPlugins = (filePath)=>/\.[jt]sx$/u.test(filePath) ? [
|
|
6
|
+
"typescript",
|
|
7
|
+
'decorators',
|
|
8
|
+
'decoratorAutoAccessors',
|
|
9
|
+
'jsx'
|
|
10
|
+
] : [
|
|
11
|
+
"typescript",
|
|
12
|
+
'decorators',
|
|
13
|
+
'decoratorAutoAccessors'
|
|
14
|
+
];
|
|
15
|
+
class SourceSyntaxError extends Error {
|
|
16
|
+
}
|
|
17
|
+
class SourceValidationHub extends Hub {
|
|
18
|
+
buildError(_node, message) {
|
|
19
|
+
return new SourceSyntaxError(message);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function parseSource(source, filename, syntaxMessage) {
|
|
23
|
+
try {
|
|
24
|
+
return parse(source, {
|
|
25
|
+
sourceType: 'module',
|
|
26
|
+
sourceFilename: filename,
|
|
27
|
+
plugins: consumerParserPlugins(filename)
|
|
28
|
+
});
|
|
29
|
+
} catch (cause) {
|
|
30
|
+
if ('object' == typeof cause && null !== cause && 'code' in cause && 'BABEL_PARSER_SYNTAX_ERROR' === cause.code && 'reasonCode' in cause && 'string' == typeof cause.reasonCode) throw new SourceSyntaxError(syntaxMessage ?? (cause instanceof Error ? cause.message : String(cause)));
|
|
31
|
+
throw cause;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function traverseSource(file, visitor) {
|
|
35
|
+
const program = NodePath.get({
|
|
36
|
+
hub: new SourceValidationHub(),
|
|
37
|
+
parentPath: void 0,
|
|
38
|
+
parent: file,
|
|
39
|
+
container: file,
|
|
40
|
+
key: 'program'
|
|
41
|
+
});
|
|
42
|
+
program.setContext();
|
|
43
|
+
traverse(file, visitor, program.scope, void 0, program);
|
|
44
|
+
}
|
|
45
|
+
function unwrapExpression(node, includeAssertions = false) {
|
|
46
|
+
while(__rspack_external__babel_types_6e8337dd.isTSAsExpression(node) || __rspack_external__babel_types_6e8337dd.isParenthesizedExpression(node) || __rspack_external__babel_types_6e8337dd.isTSSatisfiesExpression(node) || includeAssertions && (__rspack_external__babel_types_6e8337dd.isTSTypeAssertion(node) || __rspack_external__babel_types_6e8337dd.isTSNonNullExpression(node) || __rspack_external__babel_types_6e8337dd.isTSInstantiationExpression(node)))node = node.expression;
|
|
47
|
+
return node;
|
|
48
|
+
}
|
|
49
|
+
export { SourceSyntaxError, consumerParserPlugins, parseSource, traverseSource, unwrapExpression };
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
3
|
import node_path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import traverse, { Hub, NodePath } from "@babel/traverse";
|
|
4
|
+
import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from "./source-analysis.js";
|
|
6
5
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
7
6
|
const edge = '@modern-js/bff-effect/effect-edge';
|
|
8
7
|
const nodeRuntime = '@modern-js/bff-effect/effect';
|
|
9
|
-
const
|
|
10
|
-
const failure = 'Generated API entries must export defineEffectBff(...) or the
|
|
11
|
-
const
|
|
12
|
-
function invalidSource(message) {
|
|
13
|
-
const error = new Error(message);
|
|
14
|
-
invalidSources.add(error);
|
|
15
|
-
return error;
|
|
16
|
-
}
|
|
17
|
-
class SourceValidationHub extends Hub {
|
|
18
|
-
buildError(_node, message) {
|
|
19
|
-
return invalidSource(message);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
8
|
+
const assembly = '@modern-js/bff-effect/assembly';
|
|
9
|
+
const failure = 'Generated API entries must export defineEffectBff(...) or the native Effect BFF assembly helper with an explicitly composed handler Layer and an unshadowed executable root; entries must implement handlers through HttpApiBuilder.group.';
|
|
10
|
+
const MAX_API_SOURCE_MODULES = 256;
|
|
22
11
|
function createEffectApiImportResolver(filename) {
|
|
23
12
|
const directory = node_fs.realpathSync(node_path.dirname(filename));
|
|
24
13
|
const owner = node_fs.realpathSync(node_path.resolve(directory, '..'));
|
|
@@ -50,34 +39,14 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
50
39
|
const load = (input)=>{
|
|
51
40
|
const existing = modules.get(input.id);
|
|
52
41
|
if (existing) return existing;
|
|
53
|
-
if (modules.size >=
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
file = parse(input.source, {
|
|
57
|
-
sourceType: 'module',
|
|
58
|
-
sourceFilename: input.id,
|
|
59
|
-
plugins: [
|
|
60
|
-
"typescript"
|
|
61
|
-
]
|
|
62
|
-
});
|
|
63
|
-
} catch (cause) {
|
|
64
|
-
if ('object' == typeof cause && null !== cause && 'code' in cause && 'BABEL_PARSER_SYNTAX_ERROR' === cause.code && 'reasonCode' in cause && 'string' == typeof cause.reasonCode) throw invalidSource('Invalid API syntax');
|
|
65
|
-
throw cause;
|
|
66
|
-
}
|
|
42
|
+
if (modules.size >= MAX_API_SOURCE_MODULES || input.source.length > 1000000) throw new SourceSyntaxError('API source budget exceeded');
|
|
43
|
+
const file = parseSource(input.source, input.id, 'Invalid API syntax');
|
|
67
44
|
const module = {
|
|
68
45
|
...input,
|
|
69
46
|
file
|
|
70
47
|
};
|
|
71
48
|
modules.set(input.id, module);
|
|
72
|
-
|
|
73
|
-
hub: new SourceValidationHub(),
|
|
74
|
-
parentPath: void 0,
|
|
75
|
-
parent: file,
|
|
76
|
-
container: file,
|
|
77
|
-
key: 'program'
|
|
78
|
-
});
|
|
79
|
-
program.setContext();
|
|
80
|
-
traverse(file, {
|
|
49
|
+
traverseSource(file, {
|
|
81
50
|
enter (nodePath) {
|
|
82
51
|
paths.set(nodePath.node, nodePath);
|
|
83
52
|
owners.set(nodePath.node, module);
|
|
@@ -96,7 +65,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
96
65
|
if (binding) mutated.add(binding);
|
|
97
66
|
}
|
|
98
67
|
}
|
|
99
|
-
}
|
|
68
|
+
});
|
|
100
69
|
return module;
|
|
101
70
|
};
|
|
102
71
|
try {
|
|
@@ -105,10 +74,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
105
74
|
source,
|
|
106
75
|
resolveImport
|
|
107
76
|
});
|
|
108
|
-
const unwrap = (node)=>
|
|
109
|
-
while(__rspack_external__babel_types_6e8337dd.isParenthesizedExpression(node) || __rspack_external__babel_types_6e8337dd.isTSSatisfiesExpression(node) || __rspack_external__babel_types_6e8337dd.isTSAsExpression(node) || __rspack_external__babel_types_6e8337dd.isTSTypeAssertion(node) || __rspack_external__babel_types_6e8337dd.isTSNonNullExpression(node))node = node.expression;
|
|
110
|
-
return node;
|
|
111
|
-
};
|
|
77
|
+
const unwrap = (node)=>unwrapExpression(node, true);
|
|
112
78
|
const binding = (node)=>{
|
|
113
79
|
node = unwrap(node);
|
|
114
80
|
if (!__rspack_external__babel_types_6e8337dd.isIdentifier(node)) return;
|
|
@@ -133,15 +99,15 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
133
99
|
const namespaceSources = {
|
|
134
100
|
HttpApi: [
|
|
135
101
|
edge,
|
|
136
|
-
'effect/
|
|
102
|
+
'effect/http-api'
|
|
137
103
|
],
|
|
138
104
|
HttpApiBuilder: [
|
|
139
105
|
edge,
|
|
140
|
-
'effect/
|
|
106
|
+
'effect/http-api'
|
|
141
107
|
],
|
|
142
108
|
HttpRouter: [
|
|
143
109
|
edge,
|
|
144
|
-
'effect/
|
|
110
|
+
'effect/http'
|
|
145
111
|
]
|
|
146
112
|
};
|
|
147
113
|
const native = (node, name, sources)=>{
|
|
@@ -237,7 +203,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
237
203
|
if (!__rspack_external__babel_types_6e8337dd.isBlockStatement(body)) return body;
|
|
238
204
|
const statements = body.body;
|
|
239
205
|
const last = statements.at(-1);
|
|
240
|
-
return statements.slice(0, -1).every((statement)=>__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement)) && __rspack_external__babel_types_6e8337dd.isReturnStatement(last) ? last.argument : void 0;
|
|
206
|
+
return statements.slice(0, -1).every((statement)=>__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isTSTypeAliasDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isTSInterfaceDeclaration(statement)) && __rspack_external__babel_types_6e8337dd.isReturnStatement(last) ? last.argument : void 0;
|
|
241
207
|
};
|
|
242
208
|
const pipe = (node, check)=>__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) && !node.callee.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property, {
|
|
243
209
|
name: 'pipe'
|
|
@@ -302,7 +268,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
302
268
|
const groupValue = externalValue(group);
|
|
303
269
|
const groupInit = groupValue && initialized(groupValue);
|
|
304
270
|
if (!groupInit || !__rspack_external__babel_types_6e8337dd.isCallExpression(groupInit) || !method(groupInit.callee, 'RpcGroup', 'make', [
|
|
305
|
-
'effect/
|
|
271
|
+
'effect/rpc',
|
|
306
272
|
'@modern-js/bff-effect/effect-client'
|
|
307
273
|
])) return false;
|
|
308
274
|
const layerInit = initialized(layer) ?? unwrap(layer);
|
|
@@ -326,7 +292,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
326
292
|
if (value?.name === 'defineEffectBff' && [
|
|
327
293
|
edge,
|
|
328
294
|
nodeRuntime
|
|
329
|
-
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' &&
|
|
295
|
+
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && value.specifier === assembly) {
|
|
330
296
|
if (1 !== node.arguments.length) return false;
|
|
331
297
|
const values = properties(node.arguments[0]);
|
|
332
298
|
if (!values) return false;
|
|
@@ -349,7 +315,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
349
315
|
const root = entry.file.program.body.find((statement)=>__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement));
|
|
350
316
|
return root && runtime(root.declaration) ? void 0 : failure;
|
|
351
317
|
} catch (cause) {
|
|
352
|
-
if (
|
|
318
|
+
if (cause instanceof SourceSyntaxError) return failure;
|
|
353
319
|
const message = 'object' == typeof cause && null !== cause && 'message' in cause && 'string' == typeof cause.message ? cause.message : String(cause);
|
|
354
320
|
throw new Error(`Strict Effect API analysis failed: ${message}`, {
|
|
355
321
|
cause
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function runMicroVerticalApiCheckCli(args?: string[], filesOnly?: boolean): number
|
|
1
|
+
export declare function runMicroVerticalApiCheckCli(args?: string[], filesOnly?: boolean): Promise<number>;
|
|
@@ -17,4 +17,4 @@ export type WorkspaceSourceCheckOptions = {
|
|
|
17
17
|
readonly pluralCategories?: Readonly<Record<string, readonly string[]>>;
|
|
18
18
|
};
|
|
19
19
|
export declare const runWorkspaceSourceCheck: ({ cwd, sourceRoots, locales, pluralCategories, }?: WorkspaceSourceCheckOptions) => number;
|
|
20
|
-
export declare const
|
|
20
|
+
export declare const runWorkspaceSourceCheckCli: (args?: readonly string[]) => number;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Only `.tsx` is JSX. Enabling JSX for a `.ts` file misreads a generic arrow
|
|
3
|
-
* such as `const f = <T>(value: T) => value` as an unclosed JSX element.
|
|
4
|
-
*/
|
|
5
|
-
export declare const consumerParserPlugins: (filePath: string) => ('typescript' | 'jsx')[];
|
|
6
1
|
export interface MicroVerticalApiBaselineExpectation {
|
|
7
2
|
/** Optional override for operation IDs outside reachable endpoint naming conventions. */
|
|
8
3
|
readonly operationPaths?: Readonly<Record<string, string>>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { type ModuleGraph, type SourceModule } from './module-graph.js';
|
|
1
2
|
export type { MicroVerticalApiBaselineExpectation, MicroVerticalTopologyEntry, } from './microvertical-api-baseline.js';
|
|
2
3
|
export { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation, } from './microvertical-api-baseline.js';
|
|
4
|
+
export type { GraphValue, ModuleGraph, ModuleGraphHop, ResolvedBinding, SourceModule, } from './module-graph.js';
|
|
5
|
+
export { createModuleGraph } from './module-graph.js';
|
|
3
6
|
export interface MicroVerticalConfiguredApp {
|
|
4
7
|
readonly path: string;
|
|
5
8
|
readonly id?: string;
|
|
@@ -15,11 +18,22 @@ export interface MicroVerticalConfiguredApp {
|
|
|
15
18
|
readonly additionalPaths?: Readonly<Record<string, string>>;
|
|
16
19
|
};
|
|
17
20
|
}
|
|
21
|
+
export interface MicroVerticalApiSourceRuleContext {
|
|
22
|
+
/** Workspace-relative path of `module`. */
|
|
23
|
+
readonly file: string;
|
|
24
|
+
readonly module: SourceModule;
|
|
25
|
+
/** One graph per check, shared by every rule and source file. */
|
|
26
|
+
readonly graph: ModuleGraph;
|
|
27
|
+
}
|
|
28
|
+
/** A consumer-owned source rule; each returned message is a violation. */
|
|
29
|
+
export type MicroVerticalApiSourceRule = (context: MicroVerticalApiSourceRuleContext) => readonly string[];
|
|
18
30
|
export interface MicroVerticalApiCheckOptions {
|
|
19
31
|
readonly workspaceRoot: string;
|
|
20
32
|
readonly configuredApps?: readonly MicroVerticalConfiguredApp[];
|
|
21
|
-
/**
|
|
33
|
+
/** Exact installed owner constraint; otherwise resolve each contract’s native owner. */
|
|
22
34
|
readonly baselinePackageDirectory?: string;
|
|
35
|
+
/** Rules run once per workspace TypeScript/JavaScript source file. */
|
|
36
|
+
readonly sourceRules?: readonly MicroVerticalApiSourceRule[];
|
|
23
37
|
}
|
|
24
38
|
export interface MicroVerticalApiCheckResult {
|
|
25
39
|
readonly diagnostics: readonly string[];
|
|
@@ -3,4 +3,4 @@ export declare const baselinePackage = "@modern-js/bff-effect/microvertical-api"
|
|
|
3
3
|
/** Locate the installed package from this consumer's resolver, never this tool's installation. */
|
|
4
4
|
export declare function resolveBaselinePackageDirectory(fromFile: string): string;
|
|
5
5
|
/** Follow only public bindings and local barrels. Never examine schema implementation expressions. */
|
|
6
|
-
export declare function baselinePublicIdentityIsExact(fromFile: string, expectation: MicroVerticalApiBaselineExpectation): boolean;
|
|
6
|
+
export declare function baselinePublicIdentityIsExact(fromFile: string, expectation: MicroVerticalApiBaselineExpectation, reportMismatch?: (message: string) => void): boolean;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
/** A parsed workspace module and the package directory it may traverse privately. */
|
|
3
|
+
export interface SourceModule {
|
|
4
|
+
readonly file: t.File;
|
|
5
|
+
/** Real absolute path. */
|
|
6
|
+
readonly path: string;
|
|
7
|
+
/** Real package directory; relative imports may not leave it. */
|
|
8
|
+
readonly boundary: string;
|
|
9
|
+
/**
|
|
10
|
+
* Every assignment, update and namespace merge in the module. Validation
|
|
11
|
+
* that reads initializers directly must reject a module that has any.
|
|
12
|
+
*/
|
|
13
|
+
readonly mutations: readonly t.Node[];
|
|
14
|
+
}
|
|
15
|
+
/** One `name` looked up in the module at `path`, in traversal order. */
|
|
16
|
+
export interface ModuleGraphHop {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Where a binding ends up. `declaration` is a workspace `const` initializer;
|
|
22
|
+
* `external` is a named import or re-export from a package outside the
|
|
23
|
+
* workspace source graph. `chain` starts at the requesting module and ends at
|
|
24
|
+
* the declaring module (or the last workspace module before the package).
|
|
25
|
+
*/
|
|
26
|
+
export type ResolvedBinding = {
|
|
27
|
+
readonly kind: 'declaration';
|
|
28
|
+
readonly expression: t.Expression;
|
|
29
|
+
readonly module: SourceModule;
|
|
30
|
+
readonly chain: readonly ModuleGraphHop[];
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: 'external';
|
|
33
|
+
readonly specifier: string;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly chain: readonly ModuleGraphHop[];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* One value an expression can evaluate to.
|
|
39
|
+
* - `external`: `name` imported from package `specifier`, then `members`
|
|
40
|
+
* accessed on it (`Schema.Unknown` is `effect` / `Schema` / `['Unknown']`;
|
|
41
|
+
* a namespace import of the package itself is `name: '*'`).
|
|
42
|
+
* - `node`: a workspace expression, function or class the source spells out
|
|
43
|
+
* (a call to an external factory, a literal, a function).
|
|
44
|
+
* - `unresolved`: a parameter, global, dynamic access or anything beyond the
|
|
45
|
+
* analysis budget. Rules that must be sound treat it as a violation.
|
|
46
|
+
*/
|
|
47
|
+
export type GraphValue = {
|
|
48
|
+
readonly kind: 'external';
|
|
49
|
+
readonly specifier: string;
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly members: readonly string[];
|
|
52
|
+
} | {
|
|
53
|
+
readonly kind: 'node' | 'unresolved';
|
|
54
|
+
readonly node: t.Node;
|
|
55
|
+
readonly module: SourceModule;
|
|
56
|
+
};
|
|
57
|
+
export interface ModuleGraph {
|
|
58
|
+
/**
|
|
59
|
+
* The parsed workspace module at `filePath`, parsed once per graph. Throws
|
|
60
|
+
* `SourceSyntaxError` when the file is not readable consumer source.
|
|
61
|
+
*/
|
|
62
|
+
module(filePath: string): SourceModule;
|
|
63
|
+
/**
|
|
64
|
+
* Follow a local binding (`scope: 'local'`) or an export (`'export'`) of
|
|
65
|
+
* `module` through relative imports, re-exports and public workspace
|
|
66
|
+
* package exports to an unmutated `const`. Unresolvable, mutated, cyclic or
|
|
67
|
+
* over-budget bindings return undefined so callers fail closed.
|
|
68
|
+
*/
|
|
69
|
+
resolve(module: SourceModule, name: string, scope: 'export' | 'local'): ResolvedBinding | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Every value `expression` (a node of `module`) can evaluate to, following
|
|
72
|
+
* any binding kind, destructuring, member access into object literals,
|
|
73
|
+
* member assignments (`obj.key = value`), reassignments, conditional
|
|
74
|
+
* branches, and calls into workspace functions through their returns.
|
|
75
|
+
*/
|
|
76
|
+
evaluate(module: SourceModule, expression: t.Node): readonly GraphValue[];
|
|
77
|
+
/**
|
|
78
|
+
* Every `external` and `unresolved` value reachable from `expression`: each
|
|
79
|
+
* reference inside it is evaluated, and every workspace node it evaluates
|
|
80
|
+
* to is walked in turn, across modules. `Schema.Struct({ a: shared })`
|
|
81
|
+
* reaches `Schema.Struct` plus whatever `shared` is built from.
|
|
82
|
+
*/
|
|
83
|
+
reachable(module: SourceModule, expression: t.Node): readonly GraphValue[];
|
|
84
|
+
}
|
|
85
|
+
export declare const propertyName: (node: t.Node | undefined) => string | undefined;
|
|
86
|
+
export declare const localConst: (sourceFile: t.File, name: string) => t.VariableDeclarator | undefined;
|
|
87
|
+
export declare const exportedConst: (sourceFile: t.File, name: string) => t.VariableDeclarator | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* The workspace module graph. Each module is parsed once per graph; an
|
|
90
|
+
* unreadable module stays unresolved instead of assumed valid.
|
|
91
|
+
*/
|
|
92
|
+
export declare function createModuleGraph(): ModuleGraph;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Visitor } from '@babel/traverse';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
/**
|
|
4
|
+
* Only JSX extensions enable JSX: generic arrows in .ts must remain valid.
|
|
5
|
+
* Decorators are standard TypeScript syntax.
|
|
6
|
+
*/
|
|
7
|
+
export declare const consumerParserPlugins: (filePath: string) => ('typescript' | 'jsx' | 'decorators' | 'decoratorAutoAccessors')[];
|
|
8
|
+
/** Only parser/binder diagnostics are policy violations; tool failures escape. */
|
|
9
|
+
export declare class SourceSyntaxError extends Error {
|
|
10
|
+
}
|
|
11
|
+
export declare function parseSource(source: string, filename: string, syntaxMessage?: string): t.File;
|
|
12
|
+
export declare function traverseSource(file: t.File, visitor: Visitor): void;
|
|
13
|
+
export declare function unwrapExpression(node: t.Node, includeAssertions?: boolean): t.Node;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bleedingdev/modern-js-code-tools",
|
|
3
3
|
"description": "Code analysis tooling for Modern.js projects.",
|
|
4
|
-
"homepage": "https://github.com/
|
|
4
|
+
"homepage": "https://github.com/ultramodernjs/ultramodern.js#readme",
|
|
5
5
|
"bugs": {
|
|
6
|
-
"url": "https://github.com/
|
|
6
|
+
"url": "https://github.com/ultramodernjs/ultramodern.js/issues"
|
|
7
7
|
},
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/ultramodernjs/ultramodern.js.git",
|
|
11
11
|
"directory": "packages/toolkit/code-tools"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=20"
|
|
25
25
|
},
|
|
26
|
-
"version": "3.9.
|
|
26
|
+
"version": "3.9.3-ultramodern.1",
|
|
27
27
|
"types": "./dist/types/index.d.ts",
|
|
28
28
|
"main": "./dist/esm-node/index.js",
|
|
29
29
|
"typesVersions": {
|
|
@@ -72,16 +72,16 @@
|
|
|
72
72
|
"bin"
|
|
73
73
|
],
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@babel/parser": "^8.0.
|
|
76
|
-
"@babel/traverse": "^8.0.
|
|
77
|
-
"@babel/types": "^8.0.
|
|
78
|
-
"oxlint": "1.
|
|
75
|
+
"@babel/parser": "^8.0.6",
|
|
76
|
+
"@babel/traverse": "^8.0.6",
|
|
77
|
+
"@babel/types": "^8.0.6",
|
|
78
|
+
"oxlint": "1.86.0",
|
|
79
|
+
"resolve.exports": "2.0.3"
|
|
79
80
|
},
|
|
80
81
|
"devDependencies": {
|
|
81
|
-
"@rslib/core": "1.0.
|
|
82
|
+
"@rslib/core": "1.0.3",
|
|
82
83
|
"@scripts/rstest-config": "2.66.0",
|
|
83
|
-
"@types/node": "^26.
|
|
84
|
-
"@typescript/native-preview": "7.0.0-dev.20260707.2"
|
|
84
|
+
"@types/node": "^26.6.3"
|
|
85
85
|
},
|
|
86
86
|
"sideEffects": false,
|
|
87
87
|
"publishConfig": {
|
|
@@ -90,11 +90,15 @@
|
|
|
90
90
|
"modern:source": "./src/index.ts",
|
|
91
91
|
"bin": {
|
|
92
92
|
"modern-api-check": "bin/modern-api-check.mjs",
|
|
93
|
-
"modern-api-check-files": "bin/modern-api-check-files.mjs"
|
|
93
|
+
"modern-api-check-files": "bin/modern-api-check-files.mjs",
|
|
94
|
+
"modern-i18n-check": "bin/modern-i18n-check.mjs"
|
|
94
95
|
},
|
|
95
96
|
"scripts": {
|
|
96
97
|
"build": "rm -rf dist && rslib build -c rslib.config.mts",
|
|
97
98
|
"dev": "rslib build -c rslib.config.mts -w",
|
|
98
|
-
"test": "rstest
|
|
99
|
+
"test": "rstest"
|
|
100
|
+
},
|
|
101
|
+
"ultramodern": {
|
|
102
|
+
"sourceName": "@modern-js/code-tools"
|
|
99
103
|
}
|
|
100
104
|
}
|