@orkestrel/scaffold 0.0.4 → 0.0.6

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