@orkestrel/scaffold 0.0.5 → 0.0.7

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.
@@ -51,10 +51,47 @@ export const DATA_SOURCE_FILES: readonly string[] = Object.freeze([
51
51
  'contracts.ts',
52
52
  'relations.ts',
53
53
  'schemas.ts',
54
+ 'shapers.ts',
54
55
  'templates.ts',
55
56
  'validators.ts',
56
57
  ])
57
58
 
59
+ /** Worker-only value globals that WebWorker typing must not expose to core implementations. */
60
+ export const WORKER_SCOPE_VALUE_GLOBALS: readonly string[] = Object.freeze([
61
+ 'name',
62
+ 'onrtctransform',
63
+ 'close',
64
+ 'postMessage',
65
+ 'dispatchEvent',
66
+ 'location',
67
+ 'onerror',
68
+ 'onlanguagechange',
69
+ 'onoffline',
70
+ 'ononline',
71
+ 'onrejectionhandled',
72
+ 'onunhandledrejection',
73
+ 'self',
74
+ 'importScripts',
75
+ 'fonts',
76
+ 'caches',
77
+ 'crossOriginIsolated',
78
+ 'indexedDB',
79
+ 'isSecureContext',
80
+ 'origin',
81
+ 'scheduler',
82
+ 'createImageBitmap',
83
+ 'reportError',
84
+ 'cancelAnimationFrame',
85
+ 'requestAnimationFrame',
86
+ 'onmessage',
87
+ 'onmessageerror',
88
+ 'addEventListener',
89
+ 'removeEventListener',
90
+ ])
91
+
92
+ /** Virtual source text used while binding one policy-inspected module. */
93
+ export const POLICY_SOURCE_TEXTS: Map<string, string> = new Map()
94
+
58
95
  /** One script block extracted from a Vue SFC by the official compiler. */
59
96
  export interface VueScriptBlockInterface {
60
97
  readonly content: string
@@ -162,6 +199,126 @@ export function hasAllowedTripleSlashReference(path: string, source: ts.SourceFi
162
199
  )
163
200
  }
164
201
 
202
+ /**
203
+ * Whether the policy compiler can read one source path.
204
+ *
205
+ * @param path - The source path to inspect
206
+ * @returns `true` when the virtual or physical source exists
207
+ */
208
+ export function hasPolicySource(path: string): boolean {
209
+ return POLICY_SOURCE_TEXTS.has(path) || ts.sys.fileExists(path)
210
+ }
211
+
212
+ /**
213
+ * Read one virtual or physical policy source.
214
+ *
215
+ * @param path - The source path to read
216
+ * @returns The source text when present
217
+ */
218
+ export function readPolicySource(path: string): string | undefined {
219
+ return POLICY_SOURCE_TEXTS.get(path) ?? ts.sys.readFile(path)
220
+ }
221
+
222
+ /**
223
+ * Parse one virtual or physical policy source for the compiler host.
224
+ *
225
+ * @param path - The source path to parse
226
+ * @param language - The requested TypeScript language target
227
+ * @returns The parsed source file when present
228
+ */
229
+ export function createPolicySource(
230
+ path: string,
231
+ language: ts.ScriptTarget | ts.CreateSourceFileOptions,
232
+ ): ts.SourceFile | undefined {
233
+ const content = readPolicySource(path)
234
+ return content === undefined ? undefined : ts.createSourceFile(path, content, language, true)
235
+ }
236
+
237
+ /**
238
+ * Bind one policy-inspected module without loading ambient host declarations.
239
+ *
240
+ * @param path - The source path used in diagnostics
241
+ * @param content - The TypeScript source text to bind
242
+ * @returns A one-file TypeScript program whose checker resolves lexical bindings
243
+ */
244
+ export function createPolicyProgram(path: string, content: string): ts.Program {
245
+ const options: ts.CompilerOptions = {
246
+ allowJs: true,
247
+ noLib: true,
248
+ noResolve: true,
249
+ target: ts.ScriptTarget.Latest,
250
+ types: [],
251
+ }
252
+ POLICY_SOURCE_TEXTS.set(path, content)
253
+ const host = ts.createCompilerHost(options)
254
+ host.fileExists = hasPolicySource
255
+ host.readFile = readPolicySource
256
+ host.getSourceFile = createPolicySource
257
+ const program = ts.createProgram([path], options, host)
258
+ POLICY_SOURCE_TEXTS.delete(path)
259
+ return program
260
+ }
261
+
262
+ /**
263
+ * Whether an identifier is a standalone runtime value reference.
264
+ *
265
+ * @param node - The identifier occurrence to classify
266
+ * @param checker - The binder used to distinguish lexical values from ambient globals
267
+ * @returns `true` only when the occurrence reads or writes a runtime value
268
+ */
269
+ export function isValueReferenceIdentifier(node: ts.Identifier, checker: ts.TypeChecker): boolean {
270
+ if (ts.isPartOfTypeNode(node)) return false
271
+
272
+ let ancestor: ts.Node = node.parent
273
+ while (!ts.isSourceFile(ancestor) && !ts.isStatement(ancestor)) {
274
+ if (ts.isTypeQueryNode(ancestor)) return false
275
+ if (ts.isComputedPropertyName(ancestor) && ts.isTypeElement(ancestor.parent)) return false
276
+ ancestor = ancestor.parent
277
+ }
278
+
279
+ const parent = node.parent
280
+ if (
281
+ (ts.isPropertyAccessExpression(parent) && parent.name === node) ||
282
+ (ts.isPropertyAssignment(parent) && parent.name === node) ||
283
+ (ts.isPropertyDeclaration(parent) && parent.name === node) ||
284
+ (ts.isPropertySignature(parent) && parent.name === node) ||
285
+ (ts.isMethodDeclaration(parent) && parent.name === node) ||
286
+ (ts.isMethodSignature(parent) && parent.name === node) ||
287
+ (ts.isGetAccessorDeclaration(parent) && parent.name === node) ||
288
+ (ts.isSetAccessorDeclaration(parent) && parent.name === node) ||
289
+ (ts.isVariableDeclaration(parent) && parent.name === node) ||
290
+ (ts.isParameter(parent) && parent.name === node) ||
291
+ (ts.isBindingElement(parent) && (parent.name === node || parent.propertyName === node)) ||
292
+ (ts.isFunctionDeclaration(parent) && parent.name === node) ||
293
+ (ts.isFunctionExpression(parent) && parent.name === node) ||
294
+ (ts.isClassDeclaration(parent) && parent.name === node) ||
295
+ (ts.isClassExpression(parent) && parent.name === node) ||
296
+ (ts.isInterfaceDeclaration(parent) && parent.name === node) ||
297
+ (ts.isTypeAliasDeclaration(parent) && parent.name === node) ||
298
+ (ts.isTypeParameterDeclaration(parent) && parent.name === node) ||
299
+ (ts.isEnumDeclaration(parent) && parent.name === node) ||
300
+ (ts.isEnumMember(parent) && parent.name === node) ||
301
+ (ts.isModuleDeclaration(parent) && parent.name === node) ||
302
+ ts.isImportClause(parent) ||
303
+ ts.isImportSpecifier(parent) ||
304
+ ts.isNamespaceImport(parent) ||
305
+ ts.isImportEqualsDeclaration(parent) ||
306
+ ts.isExportSpecifier(parent) ||
307
+ ts.isNamespaceExport(parent) ||
308
+ ts.isNamespaceExportDeclaration(parent) ||
309
+ (ts.isLabeledStatement(parent) && parent.label === node) ||
310
+ (ts.isBreakOrContinueStatement(parent) && parent.label === node) ||
311
+ (ts.isJsxAttribute(parent) && parent.name === node)
312
+ ) {
313
+ return false
314
+ }
315
+ return (
316
+ (ts.isShorthandPropertyAssignment(parent)
317
+ ? checker.getShorthandAssignmentValueSymbol(parent)
318
+ : checker.getSymbolAtLocation(node)) === undefined
319
+ )
320
+ }
321
+
165
322
  /** Inspect a Vue single-file component for syntax that can bypass declared import policy. */
166
323
  export function inspectVueCodingLaw(
167
324
  path: string,
@@ -187,7 +344,22 @@ export function inspectVueCodingLaw(
187
344
  }
188
345
 
189
346
  /** Add syntax-wide coding-law violations while traversing one source tree. */
190
- export function inspectCodingNode(path: string, node: ts.Node, violations: string[]): void {
347
+ export function inspectCodingNode(
348
+ path: string,
349
+ node: ts.Node,
350
+ violations: string[],
351
+ checker: ts.TypeChecker,
352
+ ): void {
353
+ if (
354
+ /^(?:app|src)[\\/]core[\\/]/u.test(path) &&
355
+ ts.isIdentifier(node) &&
356
+ WORKER_SCOPE_VALUE_GLOBALS.includes(node.text) &&
357
+ isValueReferenceIdentifier(node, checker)
358
+ ) {
359
+ violations.push(
360
+ `${path}:${formatPolicyPosition(node)} forbids worker-scope global ${node.text} in core`,
361
+ )
362
+ }
191
363
  if (
192
364
  ts.isAsExpression(node) ||
193
365
  ts.isTypeAssertionExpression(node) ||
@@ -242,13 +414,16 @@ export function inspectCodingNode(path: string, node: ts.Node, violations: strin
242
414
  ) {
243
415
  violations.push(`${path}:${formatPolicyPosition(node)} forbids hidden function assignments`)
244
416
  }
245
- ts.forEachChild(node, (child) => inspectCodingNode(path, child, violations))
417
+ ts.forEachChild(node, (child) => inspectCodingNode(path, child, violations, checker))
246
418
  }
247
419
 
248
420
  /** Inspect one TypeScript source module for repository coding-law violations. */
249
421
  export function inspectCodingLaw(path: string, content: string): readonly string[] {
250
422
  const violations: string[] = []
251
- const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true)
423
+ const program = createPolicyProgram(path, content)
424
+ const source = program.getSourceFile(path)
425
+ if (source === undefined) throw new Error(`Policy source was not bound at ${path}`)
426
+ const checker = program.getTypeChecker()
252
427
  const file = basename(path)
253
428
 
254
429
  if (/\.[cm]?jsx?$/u.test(path)) {
@@ -362,7 +537,7 @@ export function inspectCodingLaw(path: string, content: string): readonly string
362
537
  }
363
538
  }
364
539
 
365
- inspectCodingNode(path, source, violations)
540
+ inspectCodingNode(path, source, violations, checker)
366
541
  return violations
367
542
  }
368
543