@craft-ts/dev-tools 0.7.0-beta.13 → 0.7.0-beta.15
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/README.md +64 -10
- package/package.json +8 -2
- package/src/bin/agent-selector.d.ts +22 -0
- package/src/bin/agent-selector.js +100 -0
- package/src/bin/agent-selector.js.map +1 -0
- package/src/bin/craft-graph.js +74 -8
- package/src/bin/craft-graph.js.map +1 -1
- package/src/bin/craft.d.ts +23 -0
- package/src/bin/craft.js +245 -28
- package/src/bin/craft.js.map +1 -1
- package/src/eslint-rules/anchor-has-href.cjs +37 -3
- package/src/eslint-rules/craft-computed-name-match.cjs +26 -5
- package/src/eslint-rules/craft-name-match-utils.cjs +18 -9
- package/src/eslint-rules/hyperscript-walk.cjs +3 -3
- package/src/eslint-rules/index.cjs +46 -1
- package/src/eslint-rules/no-auth-token-in-local-storage.cjs +17 -0
- package/src/eslint-rules/no-craft-computed-side-effects.cjs +12 -5
- package/src/eslint-rules/no-craft-service-component-same-file.cjs +138 -0
- package/src/eslint-rules/no-effect-import-in-frontend.cjs +52 -0
- package/src/eslint-rules/no-effect-in-params.cjs +31 -1
- package/src/eslint-rules/no-explicit-effect-type.cjs +137 -0
- package/src/eslint-rules/no-free-has.cjs +47 -0
- package/src/eslint-rules/no-heading-level-skip.cjs +6 -6
- package/src/eslint-rules/no-raw-class.cjs +76 -0
- package/src/eslint-rules/no-raw-css-value.cjs +163 -0
- package/src/eslint-rules/no-raw-user-url.cjs +114 -0
- package/src/eslint-rules/no-trust-forwarded-headers.cjs +43 -0
- package/src/eslint-rules/no-unsafe-html.cjs +93 -0
- package/src/eslint-rules/no-unsafe-transfer-state.cjs +54 -0
- package/src/eslint-rules/prefer-craft-template-blocks.cjs +8 -8
- package/src/eslint-rules/prefer-direct-yieldable-callback.cjs +57 -14
- package/src/eslint-rules/prefer-inline-effect-insertion.cjs +263 -0
- package/src/eslint-rules/prefer-inline-route-providers.cjs +138 -0
- package/src/eslint-rules/recommended-config.cjs +6 -0
- package/src/eslint-rules/require-reactive-template-bindings.cjs +8 -8
- package/src/eslint-rules/require-route-security-policy.cjs +87 -0
- package/src/eslint-rules/require-server-function-timeout.cjs +39 -0
- package/src/eslint-rules/security-config.cjs +14 -0
- package/src/eslint-rules/security-rule-utils.cjs +21 -0
- package/src/eslint-rules/style-file-boundary.cjs +75 -0
- package/src/eslint-rules/sync-effect-body.cjs +343 -0
- package/src/index.d.ts +4 -0
- package/src/index.js +4 -0
- package/src/index.js.map +1 -1
- package/src/scripts/architecture-graph.js +8 -2
- package/src/scripts/architecture-graph.js.map +1 -1
- package/src/scripts/create/create-project.d.ts +71 -1
- package/src/scripts/create/create-project.js +1937 -176
- package/src/scripts/create/create-project.js.map +1 -1
- package/src/scripts/data-flow-graph.d.ts +1 -1
- package/src/scripts/effect-dependency-graph.d.ts +1 -1
- package/src/scripts/security-check.d.ts +30 -0
- package/src/scripts/security-check.js +223 -0
- package/src/scripts/security-check.js.map +1 -0
- package/src/scripts/style-architecture.d.ts +56 -0
- package/src/scripts/style-architecture.js +142 -0
- package/src/scripts/style-architecture.js.map +1 -0
- package/src/scripts/style-graph.d.ts +99 -0
- package/src/scripts/style-graph.js +179 -0
- package/src/scripts/style-graph.js.map +1 -0
- package/src/scripts/style-report.d.ts +55 -0
- package/src/scripts/style-report.js +67 -0
- package/src/scripts/style-report.js.map +1 -0
- package/src/scripts/template/migrate-template.js +1 -1
- package/src/scripts/template/migrate-template.js.map +1 -1
- package/src/scripts/angular-brand-codemod.ts +0 -3985
- package/src/scripts/architecture/migrate-architecture.ts +0 -653
- package/src/scripts/architecture-graph.ts +0 -3136
- package/src/scripts/components/migrate-components.ts +0 -227
- package/src/scripts/components/migration-diagnostic.ts +0 -8
- package/src/scripts/create/create-project.ts +0 -849
- package/src/scripts/data-flow-graph.ts +0 -240
- package/src/scripts/dependency-graph.ts +0 -5908
- package/src/scripts/effect-dependency-graph.ts +0 -630
- package/src/scripts/migrate.ts +0 -162
- package/src/scripts/migration-workspace.ts +0 -93
- package/src/scripts/primitives/migrate-named-primitives.ts +0 -627
- package/src/scripts/primitives/migrate-primitive-generators.ts +0 -410
- package/src/scripts/primitives/migrate-primitives.ts +0 -655
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.ts +0 -499
- package/src/scripts/primitives/migration-diagnostic.ts +0 -15
- package/src/scripts/routes/migrate-routes.ts +0 -895
- package/src/scripts/routes/migration-diagnostic.ts +0 -25
- package/src/scripts/routes/route-command.ts +0 -1807
- package/src/scripts/routes/verify-routes.ts +0 -1511
- package/src/scripts/services/config.ts +0 -60
- package/src/scripts/services/migrate-services.ts +0 -1348
- package/src/scripts/services/migration-diagnostic.ts +0 -28
- package/src/scripts/template/migrate-template.ts +0 -418
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function report(context, node, message) {
|
|
2
|
+
context.report({ node, message });
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function propertyName(node) {
|
|
6
|
+
if (!node) return undefined;
|
|
7
|
+
if (!node.computed && node.property && node.property.type === 'Identifier') {
|
|
8
|
+
return node.property.name;
|
|
9
|
+
}
|
|
10
|
+
if (node.computed && node.property && node.property.type === 'Literal') {
|
|
11
|
+
return String(node.property.value);
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isCallNamed(node, name) {
|
|
17
|
+
return node && node.type === 'CallExpression' &&
|
|
18
|
+
node.callee && node.callee.type === 'Identifier' && node.callee.name === name;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { report, propertyName, isCallNamed };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const ALLOWED_MODULES = ['@craft-ts/style'];
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A `*.style.ts` may import style vocabulary and nothing else.
|
|
5
|
+
*
|
|
6
|
+
* This is not a taste rule: the build plugin **imports these modules in Node**
|
|
7
|
+
* to read the values they register. An application import would drag component
|
|
8
|
+
* code, DI, or browser globals into that evaluation, and the emitter would
|
|
9
|
+
* either crash or — worse — succeed while running application side effects at
|
|
10
|
+
* build time.
|
|
11
|
+
*
|
|
12
|
+
* Relative imports are allowed only between style files, so a design system can
|
|
13
|
+
* be split across several of them.
|
|
14
|
+
*/
|
|
15
|
+
module.exports = {
|
|
16
|
+
meta: {
|
|
17
|
+
type: 'problem',
|
|
18
|
+
docs: {
|
|
19
|
+
description:
|
|
20
|
+
'Restrict *.style.ts files to style vocabulary imports so the build plugin can evaluate them in Node.',
|
|
21
|
+
},
|
|
22
|
+
schema: [
|
|
23
|
+
{
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
allow: { type: 'array', items: { type: 'string' } },
|
|
27
|
+
suffix: { type: 'string' },
|
|
28
|
+
},
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
messages: {
|
|
33
|
+
applicationImport:
|
|
34
|
+
"A style file may only import style vocabulary, and '{{source}}' is not. The build plugin imports this file in Node to read the values it registers; an application import would run application code at build time. Move what you need into the sheet, or into another *.style.ts.",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
create(context) {
|
|
38
|
+
const options = context.options[0] ?? {};
|
|
39
|
+
const allowed = new Set([...ALLOWED_MODULES, ...(options.allow ?? [])]);
|
|
40
|
+
const suffix = options.suffix ?? '.style.ts';
|
|
41
|
+
const filename = context.filename ?? context.getFilename();
|
|
42
|
+
|
|
43
|
+
if (!filename.endsWith(suffix)) return {};
|
|
44
|
+
|
|
45
|
+
const check = (node, source) => {
|
|
46
|
+
if (typeof source !== 'string') return;
|
|
47
|
+
if (allowed.has(source)) return;
|
|
48
|
+
// A relative import is fine as long as it lands on another style file:
|
|
49
|
+
// splitting a design system across files must stay possible.
|
|
50
|
+
if (source.startsWith('.') && source.endsWith('.style')) return;
|
|
51
|
+
if (source.startsWith('.') && source.endsWith(suffix.slice(0, -3)))
|
|
52
|
+
return;
|
|
53
|
+
context.report({
|
|
54
|
+
node,
|
|
55
|
+
messageId: 'applicationImport',
|
|
56
|
+
data: { source },
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
ImportDeclaration(node) {
|
|
62
|
+
check(node, node.source.value);
|
|
63
|
+
},
|
|
64
|
+
ExportNamedDeclaration(node) {
|
|
65
|
+
if (node.source) check(node, node.source.value);
|
|
66
|
+
},
|
|
67
|
+
ExportAllDeclaration(node) {
|
|
68
|
+
if (node.source) check(node, node.source.value);
|
|
69
|
+
},
|
|
70
|
+
ImportExpression(node) {
|
|
71
|
+
if (node.source.type === 'Literal') check(node, node.source.value);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// `SyncOp` in `R` is a CLAIM: an author writing `Effect<A, E, SyncOp>` promises
|
|
3
|
+
// the Effect never suspends. The type system cannot check that claim — a
|
|
4
|
+
// service member closes its dependencies at Layer construction, so an async
|
|
5
|
+
// member and a pure one both surface as `R = never` and stay indistinguishable.
|
|
6
|
+
//
|
|
7
|
+
// This rule reads the body instead, all branches at once, which is the thing a
|
|
8
|
+
// unit test cannot do: it needs no fixture, and a branch that only suspends on
|
|
9
|
+
// a cold cache is as visible as one that always does.
|
|
10
|
+
//
|
|
11
|
+
// It works by ELIMINATION. Requirements union through `Effect.gen`, so anything
|
|
12
|
+
// yielded that is itself declared synchronous carries `SyncOp` and passes for
|
|
13
|
+
// free. What is left ambiguous is `R = never`: either a pure constructor
|
|
14
|
+
// (`Effect.succeed`) or an async member whose Layer swallowed its dependencies.
|
|
15
|
+
// The first list is short and closed, so it is the one spelled out here; the
|
|
16
|
+
// rest is refused, and the author either declares it or moves it to a loader.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const EFFECT_PACKAGE = 'effect';
|
|
20
|
+
const CRAFT_EFFECT_PACKAGE = '@craft-ts/effect';
|
|
21
|
+
|
|
22
|
+
/** Constructors that cannot suspend — the closed list `R = never` needs. */
|
|
23
|
+
const PURE_EFFECT_CONSTRUCTORS = new Set([
|
|
24
|
+
'succeed',
|
|
25
|
+
'sync',
|
|
26
|
+
'fail',
|
|
27
|
+
'failSync',
|
|
28
|
+
'die',
|
|
29
|
+
'dieSync',
|
|
30
|
+
'void',
|
|
31
|
+
'try',
|
|
32
|
+
'succeedNone',
|
|
33
|
+
'succeedSome',
|
|
34
|
+
'fromNullable',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/** Constructors that always suspend — named so the message can be specific. */
|
|
38
|
+
const ASYNC_EFFECT_CONSTRUCTORS = new Set([
|
|
39
|
+
'promise',
|
|
40
|
+
'tryPromise',
|
|
41
|
+
'sleep',
|
|
42
|
+
'async',
|
|
43
|
+
'callback',
|
|
44
|
+
'never',
|
|
45
|
+
'delay',
|
|
46
|
+
'timeout',
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const SYNC_OP_PATTERN = /\bSyncOp\b/;
|
|
50
|
+
|
|
51
|
+
/** `ts.SymbolFlags.Class`. */
|
|
52
|
+
const SYMBOL_FLAG_CLASS = 32;
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
meta: {
|
|
56
|
+
type: 'problem',
|
|
57
|
+
docs: {
|
|
58
|
+
description:
|
|
59
|
+
'Keep a body declared synchronous (SyncOp) free of anything that may suspend.',
|
|
60
|
+
},
|
|
61
|
+
schema: [],
|
|
62
|
+
messages: {
|
|
63
|
+
asyncConstructor:
|
|
64
|
+
'Effect.{{name}}(...) always suspends: it cannot appear in a body declared synchronous (SyncOp).\nMove the asynchronous work to a loader (queryEffect / mutationEffect / asyncProcessEffect).',
|
|
65
|
+
notDeclaredSync:
|
|
66
|
+
'This body is declared synchronous (SyncOp) but yields {{what}}, which nothing declares synchronous.\nAdd SyncOp to its requirements if it is pure, or move the work to a loader (queryEffect / mutationEffect / asyncProcessEffect).',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
create(context) {
|
|
71
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
72
|
+
const parserServices = sourceCode.parserServices ?? context.parserServices;
|
|
73
|
+
const checker = parserServices?.program?.getTypeChecker?.();
|
|
74
|
+
const nodeMap = parserServices?.esTreeNodeToTSNodeMap;
|
|
75
|
+
const keys = sourceCode.visitorKeys ?? {};
|
|
76
|
+
|
|
77
|
+
const effectBindings = new Set();
|
|
78
|
+
const syncOpBindings = new Set();
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
ImportDeclaration(node) {
|
|
82
|
+
if (node.importKind === 'type') return;
|
|
83
|
+
const source = node.source.value;
|
|
84
|
+
|
|
85
|
+
for (const specifier of node.specifiers) {
|
|
86
|
+
if (
|
|
87
|
+
specifier.type === 'ImportNamespaceSpecifier' &&
|
|
88
|
+
source === EFFECT_PACKAGE
|
|
89
|
+
) {
|
|
90
|
+
effectBindings.add(specifier.local.name);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (specifier.type !== 'ImportSpecifier') continue;
|
|
95
|
+
const imported = getIdentifierName(specifier.imported);
|
|
96
|
+
|
|
97
|
+
if (source === EFFECT_PACKAGE && imported === 'Effect') {
|
|
98
|
+
effectBindings.add(specifier.local.name);
|
|
99
|
+
}
|
|
100
|
+
if (source === CRAFT_EFFECT_PACKAGE && imported === 'SyncOp') {
|
|
101
|
+
syncOpBindings.add(specifier.local.name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
CallExpression(node) {
|
|
107
|
+
if (!isEffectMemberCall(node.callee, 'gen')) return;
|
|
108
|
+
|
|
109
|
+
const body = node.arguments.find((argument) =>
|
|
110
|
+
['FunctionExpression', 'ArrowFunctionExpression'].includes(
|
|
111
|
+
argument?.type,
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
if (!body) return;
|
|
115
|
+
|
|
116
|
+
const delegatedYields = collectDelegatedYields(body);
|
|
117
|
+
if (!isDeclaredSynchronous(node, delegatedYields)) return;
|
|
118
|
+
|
|
119
|
+
for (const yielded of delegatedYields) {
|
|
120
|
+
inspectYield(yielded);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// A body is declared synchronous either by yielding the marker itself — the
|
|
126
|
+
// spelling for an inferred `R` — or by the shape it is contextually typed
|
|
127
|
+
// against already spelling `SyncOp`, in which case the implementation needs
|
|
128
|
+
// no ceremony at all.
|
|
129
|
+
function isDeclaredSynchronous(genCall, delegatedYields) {
|
|
130
|
+
if (delegatedYields.some(isSyncOpReference)) return true;
|
|
131
|
+
return contextualTypeDeclaresSync(genCall);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function contextualTypeDeclaresSync(genCall) {
|
|
135
|
+
if (!checker || !nodeMap) return false;
|
|
136
|
+
|
|
137
|
+
// Walk out to the function whose return type the shape constrains.
|
|
138
|
+
let current = genCall.parent;
|
|
139
|
+
while (
|
|
140
|
+
current &&
|
|
141
|
+
!['ArrowFunctionExpression', 'FunctionExpression'].includes(
|
|
142
|
+
current.type,
|
|
143
|
+
)
|
|
144
|
+
) {
|
|
145
|
+
if (current.type === 'CallExpression') return false;
|
|
146
|
+
current = current.parent;
|
|
147
|
+
}
|
|
148
|
+
if (!current) return false;
|
|
149
|
+
|
|
150
|
+
const tsNode = nodeMap.get(current);
|
|
151
|
+
if (!tsNode) return false;
|
|
152
|
+
|
|
153
|
+
const contextual = checker.getContextualType?.(tsNode);
|
|
154
|
+
return Boolean(contextual && SYNC_OP_PATTERN.test(safeToString(contextual)));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function inspectYield(yielded) {
|
|
158
|
+
if (isSyncOpReference(yielded)) return;
|
|
159
|
+
|
|
160
|
+
const asyncConstructor = getEffectConstructorName(
|
|
161
|
+
yielded,
|
|
162
|
+
ASYNC_EFFECT_CONSTRUCTORS,
|
|
163
|
+
);
|
|
164
|
+
if (asyncConstructor) {
|
|
165
|
+
context.report({
|
|
166
|
+
node: yielded,
|
|
167
|
+
messageId: 'asyncConstructor',
|
|
168
|
+
data: { name: asyncConstructor },
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (getEffectConstructorName(yielded, PURE_EFFECT_CONSTRUCTORS)) return;
|
|
174
|
+
// `yield* SomeService` resolves a tag from the context in place; it is the
|
|
175
|
+
// Layer that already ran, not a suspension.
|
|
176
|
+
if (isServiceTagReference(yielded)) return;
|
|
177
|
+
if (typeDeclaresSync(yielded)) return;
|
|
178
|
+
// Without type information the rule cannot tell a pure member from an
|
|
179
|
+
// async one, and guessing either way is worse than staying silent.
|
|
180
|
+
if (!checker || !nodeMap) return;
|
|
181
|
+
// Not an Effect at all (a craft primitive relayed through the body).
|
|
182
|
+
if (!isEffectType(yielded)) return;
|
|
183
|
+
|
|
184
|
+
context.report({
|
|
185
|
+
node: yielded,
|
|
186
|
+
messageId: 'notDeclaredSync',
|
|
187
|
+
data: { what: describe(yielded) },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isSyncOpReference(node) {
|
|
192
|
+
return node?.type === 'Identifier' && syncOpBindings.has(node.name);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isEffectMemberCall(callee, memberName) {
|
|
196
|
+
return (
|
|
197
|
+
callee?.type === 'MemberExpression' &&
|
|
198
|
+
!callee.computed &&
|
|
199
|
+
callee.object.type === 'Identifier' &&
|
|
200
|
+
effectBindings.has(callee.object.name) &&
|
|
201
|
+
getIdentifierName(callee.property) === memberName
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getEffectConstructorName(node, names) {
|
|
206
|
+
const call = node?.type === 'CallExpression' ? node : undefined;
|
|
207
|
+
const callee = call ? call.callee : node;
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
callee?.type !== 'MemberExpression' ||
|
|
211
|
+
callee.computed ||
|
|
212
|
+
callee.object.type !== 'Identifier' ||
|
|
213
|
+
!effectBindings.has(callee.object.name)
|
|
214
|
+
) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const name = getIdentifierName(callee.property);
|
|
219
|
+
return name && names.has(name) ? name : undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// A bare identifier that resolves to a CLASS is a `Context.Service` tag:
|
|
223
|
+
// `yield* Pricing` reads it from the context in place — the Layer already
|
|
224
|
+
// ran. A bare identifier merely holding an Effect is not a class, and falls
|
|
225
|
+
// through to the type check below.
|
|
226
|
+
function isServiceTagReference(node) {
|
|
227
|
+
if (node?.type !== 'Identifier' || !checker || !nodeMap) return false;
|
|
228
|
+
|
|
229
|
+
const tsNode = nodeMap.get(node);
|
|
230
|
+
if (!tsNode) return false;
|
|
231
|
+
|
|
232
|
+
const symbol = checker.getSymbolAtLocation(tsNode);
|
|
233
|
+
// ts.SyntaxFlags.Class — compared numerically to keep the rule free of a
|
|
234
|
+
// typescript import, like the other typed rules in this folder.
|
|
235
|
+
return Boolean(symbol && (symbol.flags & SYMBOL_FLAG_CLASS) !== 0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function typeDeclaresSync(node) {
|
|
239
|
+
if (!checker || !nodeMap) return false;
|
|
240
|
+
const type = getType(node);
|
|
241
|
+
return Boolean(type && SYNC_OP_PATTERN.test(safeToString(type)));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isEffectType(node) {
|
|
245
|
+
const type = getType(node);
|
|
246
|
+
if (!type) return false;
|
|
247
|
+
const text = safeToString(type);
|
|
248
|
+
if (/\bEffect(?:\.Effect)?\s*</.test(text)) return true;
|
|
249
|
+
const symbol = type.aliasSymbol ?? type.getSymbol?.();
|
|
250
|
+
const name = symbol && String(symbol.escapedName ?? symbol.name);
|
|
251
|
+
return name === 'Effect';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function getType(node) {
|
|
255
|
+
if (!checker || !nodeMap || !node) return undefined;
|
|
256
|
+
const tsNode = nodeMap.get(node);
|
|
257
|
+
if (!tsNode) return undefined;
|
|
258
|
+
try {
|
|
259
|
+
return checker.getTypeAtLocation(tsNode);
|
|
260
|
+
} catch {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function safeToString(type) {
|
|
266
|
+
try {
|
|
267
|
+
return checker.typeToString(type);
|
|
268
|
+
} catch {
|
|
269
|
+
return '';
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function describe(node) {
|
|
274
|
+
const text = sourceCode.getText(node);
|
|
275
|
+
return text.length > 60 ? `${text.slice(0, 57)}…` : text;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function collectDelegatedYields(functionNode) {
|
|
279
|
+
const found = [];
|
|
280
|
+
walk(
|
|
281
|
+
functionNode,
|
|
282
|
+
(child) => {
|
|
283
|
+
// A nested `Effect.gen` carries its own declaration and gets its own
|
|
284
|
+
// visit — descending into it here would blame the wrong body.
|
|
285
|
+
if (child !== functionNode && isEffectGenCall(child)) {
|
|
286
|
+
return 'skip';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (child.type === 'YieldExpression' && child.delegate) {
|
|
290
|
+
const argument = unwrap(child.argument);
|
|
291
|
+
if (argument) found.push(argument);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return undefined;
|
|
295
|
+
},
|
|
296
|
+
keys,
|
|
297
|
+
);
|
|
298
|
+
return found;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function isEffectGenCall(node) {
|
|
302
|
+
return node.type === 'CallExpression' && isEffectMemberCall(node.callee, 'gen');
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
function getIdentifierName(node) {
|
|
308
|
+
if (!node) return undefined;
|
|
309
|
+
if (node.type === 'Identifier') return node.name;
|
|
310
|
+
if (node.type === 'Literal' && typeof node.value === 'string') {
|
|
311
|
+
return node.value;
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function unwrap(node) {
|
|
317
|
+
let current = node;
|
|
318
|
+
while (
|
|
319
|
+
current &&
|
|
320
|
+
(current.type === 'TSAsExpression' ||
|
|
321
|
+
current.type === 'TSNonNullExpression' ||
|
|
322
|
+
current.type === 'TSSatisfiesExpression')
|
|
323
|
+
) {
|
|
324
|
+
current = current.expression;
|
|
325
|
+
}
|
|
326
|
+
return current;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function walk(node, visit, keys) {
|
|
330
|
+
if (!node || typeof node.type !== 'string') return;
|
|
331
|
+
if (visit(node) === 'skip') return;
|
|
332
|
+
|
|
333
|
+
const childKeys = keys[node.type] ?? Object.keys(node);
|
|
334
|
+
for (const key of childKeys) {
|
|
335
|
+
if (key === 'parent') continue;
|
|
336
|
+
const value = node[key];
|
|
337
|
+
if (Array.isArray(value)) {
|
|
338
|
+
for (const child of value) walk(child, visit, keys);
|
|
339
|
+
} else if (value && typeof value.type === 'string') {
|
|
340
|
+
walk(value, visit, keys);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -17,5 +17,9 @@ export * from './scripts/components/migration-diagnostic.js';
|
|
|
17
17
|
export * from './scripts/architecture/migrate-architecture.js';
|
|
18
18
|
export * from './scripts/dependency-graph.js';
|
|
19
19
|
export * from './scripts/architecture-graph.js';
|
|
20
|
+
export * from './scripts/style-graph.js';
|
|
21
|
+
export * from './scripts/style-architecture.js';
|
|
22
|
+
export * from './scripts/style-report.js';
|
|
23
|
+
export * from './scripts/security-check.js';
|
|
20
24
|
export * from './scripts/create/create-project.js';
|
|
21
25
|
export * from './template-migration.js';
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,10 @@ export * from './scripts/components/migration-diagnostic.js';
|
|
|
18
18
|
export * from './scripts/architecture/migrate-architecture.js';
|
|
19
19
|
export * from './scripts/dependency-graph.js';
|
|
20
20
|
export * from './scripts/architecture-graph.js';
|
|
21
|
+
export * from './scripts/style-graph.js';
|
|
22
|
+
export * from './scripts/style-architecture.js';
|
|
23
|
+
export * from './scripts/style-report.js';
|
|
24
|
+
export * from './scripts/security-check.js';
|
|
21
25
|
export * from './scripts/create/create-project.js';
|
|
22
26
|
export * from './template-migration.js';
|
|
23
27
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/dev-tools/src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,sDAAsD,CAAC;AACrE,cAAc,0DAA0D,CAAC;AACzE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/dev-tools/src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,sDAAsD,CAAC;AACrE,cAAc,0DAA0D,CAAC;AACzE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oCAAoC,CAAC;AACnD,cAAc,yBAAyB,CAAC"}
|
|
@@ -1161,8 +1161,14 @@ export function primitiveLoaderRequirementViolations(graph, rule) {
|
|
|
1161
1161
|
const label = primitiveDisplayName(primitive);
|
|
1162
1162
|
if (allowed.has(label) || allowed.has(primitive.label))
|
|
1163
1163
|
return [];
|
|
1164
|
-
const loaderEdges = graph.edges.filter((edge) =>
|
|
1165
|
-
edge.
|
|
1164
|
+
const loaderEdges = graph.edges.filter((edge) => {
|
|
1165
|
+
if (edge.from !== primitive.id)
|
|
1166
|
+
return false;
|
|
1167
|
+
const role = edge.details?.['resourceRole'];
|
|
1168
|
+
return (role === 'loader' ||
|
|
1169
|
+
(primitive.details?.['primitive'] === 'computedEffect' &&
|
|
1170
|
+
role === 'computed'));
|
|
1171
|
+
});
|
|
1166
1172
|
const satisfied = loaderEdges.some((edge) => {
|
|
1167
1173
|
const target = nodesById.get(edge.to);
|
|
1168
1174
|
if (!target)
|