@chainlink/cre-sdk 1.6.0-alpha.2 → 1.6.0-alpha.4
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 +7 -1
- package/bin/cre-compile.ts +41 -12
- package/dist/sdk/report.js +0 -15
- package/package.json +3 -3
- package/scripts/run.ts +6 -1
- package/scripts/src/check-determinism.test.ts +64 -0
- package/scripts/src/check-determinism.ts +32 -0
- package/scripts/src/compile-cli-args.test.ts +32 -0
- package/scripts/src/compile-cli-args.ts +35 -0
- package/scripts/src/compile-to-js.test.ts +90 -0
- package/scripts/src/compile-to-js.ts +53 -7
- package/scripts/src/compile-to-wasm.ts +11 -5
- package/scripts/src/compile-workflow.ts +60 -13
- package/scripts/src/generate-chain-selectors.ts +9 -27
- package/scripts/src/typecheck-workflow.test.ts +77 -0
- package/scripts/src/typecheck-workflow.ts +96 -0
- package/scripts/src/validate-shared.ts +400 -0
- package/scripts/src/validate-workflow-determinism.test.ts +409 -0
- package/scripts/src/validate-workflow-determinism.ts +545 -0
- package/scripts/src/validate-workflow-runtime-compat.ts +25 -377
|
@@ -67,24 +67,16 @@
|
|
|
67
67
|
* @see https://docs.chain.link/cre/concepts/typescript-wasm-runtime
|
|
68
68
|
*/
|
|
69
69
|
|
|
70
|
-
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
71
|
-
import path from 'node:path'
|
|
72
70
|
import * as ts from 'typescript'
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
line: number
|
|
83
|
-
/** 1-based column number. */
|
|
84
|
-
column: number
|
|
85
|
-
/** Human-readable description of the violation. */
|
|
86
|
-
message: string
|
|
87
|
-
}
|
|
71
|
+
import {
|
|
72
|
+
collectLocalSourceFiles,
|
|
73
|
+
createValidationProgram,
|
|
74
|
+
createViolation,
|
|
75
|
+
formatViolations,
|
|
76
|
+
isDeclarationName,
|
|
77
|
+
toAbsolutePath,
|
|
78
|
+
type Violation,
|
|
79
|
+
} from './validate-shared'
|
|
88
80
|
|
|
89
81
|
/**
|
|
90
82
|
* Node.js built-in module specifiers that are not available in the QuickJS
|
|
@@ -134,9 +126,6 @@ const restrictedGlobalApis = new Set([
|
|
|
134
126
|
'setInterval',
|
|
135
127
|
])
|
|
136
128
|
|
|
137
|
-
/** File extensions treated as scannable source code. */
|
|
138
|
-
const sourceExtensions = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs']
|
|
139
|
-
|
|
140
129
|
/**
|
|
141
130
|
* Error thrown when one or more runtime-incompatible API usages are detected.
|
|
142
131
|
* The message includes a docs link and a formatted list of every violation
|
|
@@ -144,336 +133,18 @@ const sourceExtensions = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs',
|
|
|
144
133
|
*/
|
|
145
134
|
class WorkflowRuntimeCompatibilityError extends Error {
|
|
146
135
|
constructor(violations: Violation[]) {
|
|
147
|
-
const sortedViolations = [...violations].sort((a, b) => {
|
|
148
|
-
if (a.filePath !== b.filePath) return a.filePath.localeCompare(b.filePath)
|
|
149
|
-
if (a.line !== b.line) return a.line - b.line
|
|
150
|
-
return a.column - b.column
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
const formattedViolations = sortedViolations
|
|
154
|
-
.map((violation) => {
|
|
155
|
-
const relativePath = path.relative(process.cwd(), violation.filePath)
|
|
156
|
-
return `- ${relativePath}:${violation.line}:${violation.column} ${violation.message}`
|
|
157
|
-
})
|
|
158
|
-
.join('\n')
|
|
159
|
-
|
|
160
136
|
super(
|
|
161
137
|
`Unsupported API usage found in workflow source.
|
|
162
138
|
CRE workflows run on Javy (QuickJS), not full Node.js.
|
|
163
139
|
Use CRE capabilities instead (for example, HTTPClient instead of fetch/node:http).
|
|
164
140
|
See https://docs.chain.link/cre/concepts/typescript-wasm-runtime
|
|
165
141
|
|
|
166
|
-
${
|
|
142
|
+
${formatViolations(violations)}`,
|
|
167
143
|
)
|
|
168
144
|
this.name = 'WorkflowRuntimeCompatibilityError'
|
|
169
145
|
}
|
|
170
146
|
}
|
|
171
147
|
|
|
172
|
-
/** Resolves a file path to an absolute path using the current working directory. */
|
|
173
|
-
const toAbsolutePath = (filePath: string) => path.resolve(filePath)
|
|
174
|
-
|
|
175
|
-
const defaultValidationCompilerOptions: ts.CompilerOptions = {
|
|
176
|
-
allowJs: true,
|
|
177
|
-
checkJs: true,
|
|
178
|
-
noEmit: true,
|
|
179
|
-
skipLibCheck: true,
|
|
180
|
-
target: ts.ScriptTarget.ESNext,
|
|
181
|
-
module: ts.ModuleKind.ESNext,
|
|
182
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const formatDiagnostic = (diagnostic: ts.Diagnostic) => {
|
|
186
|
-
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')
|
|
187
|
-
if (!diagnostic.file || diagnostic.start == null) {
|
|
188
|
-
return message
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start)
|
|
192
|
-
return `${toAbsolutePath(diagnostic.file.fileName)}:${line + 1}:${character + 1} ${message}`
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Loads compiler options from the nearest tsconfig.json so validation runs
|
|
197
|
-
* against the same ambient/type environment as the workflow project.
|
|
198
|
-
*/
|
|
199
|
-
const loadClosestTsconfigCompilerOptions = (entryFilePath: string): ts.CompilerOptions | null => {
|
|
200
|
-
const configPath = ts.findConfigFile(
|
|
201
|
-
path.dirname(entryFilePath),
|
|
202
|
-
ts.sys.fileExists,
|
|
203
|
-
'tsconfig.json',
|
|
204
|
-
)
|
|
205
|
-
if (!configPath) {
|
|
206
|
-
return null
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
let unrecoverableDiagnostic: ts.Diagnostic | null = null
|
|
210
|
-
const parsed = ts.getParsedCommandLineOfConfigFile(
|
|
211
|
-
configPath,
|
|
212
|
-
{},
|
|
213
|
-
{
|
|
214
|
-
...ts.sys,
|
|
215
|
-
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
216
|
-
unrecoverableDiagnostic = diagnostic
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
if (!parsed) {
|
|
222
|
-
if (unrecoverableDiagnostic) {
|
|
223
|
-
throw new Error(
|
|
224
|
-
`Failed to parse TypeScript config for workflow validation.\n${formatDiagnostic(unrecoverableDiagnostic)}`,
|
|
225
|
-
)
|
|
226
|
-
}
|
|
227
|
-
return null
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return parsed.options
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Maps a file extension to the appropriate TypeScript {@link ts.ScriptKind}
|
|
235
|
-
* so the parser handles JSX, CommonJS, and ESM files correctly.
|
|
236
|
-
*/
|
|
237
|
-
const getScriptKind = (filePath: string): ts.ScriptKind => {
|
|
238
|
-
switch (path.extname(filePath).toLowerCase()) {
|
|
239
|
-
case '.js':
|
|
240
|
-
return ts.ScriptKind.JS
|
|
241
|
-
case '.jsx':
|
|
242
|
-
return ts.ScriptKind.JSX
|
|
243
|
-
case '.mjs':
|
|
244
|
-
return ts.ScriptKind.JS
|
|
245
|
-
case '.cjs':
|
|
246
|
-
return ts.ScriptKind.JS
|
|
247
|
-
case '.tsx':
|
|
248
|
-
return ts.ScriptKind.TSX
|
|
249
|
-
case '.mts':
|
|
250
|
-
return ts.ScriptKind.TS
|
|
251
|
-
case '.cts':
|
|
252
|
-
return ts.ScriptKind.TS
|
|
253
|
-
default:
|
|
254
|
-
return ts.ScriptKind.TS
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Creates a {@link Violation} with 1-based line and column numbers derived
|
|
260
|
-
* from a character position in the source file.
|
|
261
|
-
*/
|
|
262
|
-
const createViolation = (
|
|
263
|
-
filePath: string,
|
|
264
|
-
pos: number,
|
|
265
|
-
sourceFile: ts.SourceFile,
|
|
266
|
-
message: string,
|
|
267
|
-
): Violation => {
|
|
268
|
-
const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos)
|
|
269
|
-
return {
|
|
270
|
-
filePath: toAbsolutePath(filePath),
|
|
271
|
-
line: line + 1,
|
|
272
|
-
column: character + 1,
|
|
273
|
-
message,
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** Returns `true` if the specifier looks like a relative or absolute file path. */
|
|
278
|
-
const isRelativeImport = (specifier: string) => {
|
|
279
|
-
return specifier.startsWith('./') || specifier.startsWith('../') || specifier.startsWith('/')
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Attempts to resolve a relative import specifier to an absolute file path.
|
|
284
|
-
* Tries the path as-is first, then appends each known source extension, then
|
|
285
|
-
* looks for an index file inside the directory. Returns `null` if nothing is
|
|
286
|
-
* found on disk.
|
|
287
|
-
*/
|
|
288
|
-
const resolveRelativeImport = (fromFilePath: string, specifier: string): string | null => {
|
|
289
|
-
const basePath = specifier.startsWith('/')
|
|
290
|
-
? path.resolve(specifier)
|
|
291
|
-
: path.resolve(path.dirname(fromFilePath), specifier)
|
|
292
|
-
|
|
293
|
-
if (existsSync(basePath) && statSync(basePath).isFile()) {
|
|
294
|
-
return toAbsolutePath(basePath)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
for (const extension of sourceExtensions) {
|
|
298
|
-
const withExtension = `${basePath}${extension}`
|
|
299
|
-
if (existsSync(withExtension)) {
|
|
300
|
-
return toAbsolutePath(withExtension)
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
for (const extension of sourceExtensions) {
|
|
305
|
-
const asIndex = path.join(basePath, `index${extension}`)
|
|
306
|
-
if (existsSync(asIndex)) {
|
|
307
|
-
return toAbsolutePath(asIndex)
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return null
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Extracts a string literal from the first argument of a call expression.
|
|
316
|
-
* Used for `require('node:fs')` and `import('node:fs')` patterns.
|
|
317
|
-
* Returns `null` if the first argument is not a static string literal.
|
|
318
|
-
*/
|
|
319
|
-
const getStringLiteralFromCall = (node: ts.CallExpression): string | null => {
|
|
320
|
-
const [firstArg] = node.arguments
|
|
321
|
-
if (!firstArg || !ts.isStringLiteral(firstArg)) return null
|
|
322
|
-
return firstArg.text
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* **Pass 1 — Module import analysis.**
|
|
327
|
-
*
|
|
328
|
-
* Walks the AST of a single source file and:
|
|
329
|
-
* - Flags any import/export/require/dynamic-import of a restricted module.
|
|
330
|
-
* - Enqueues relative imports for recursive scanning so the validator
|
|
331
|
-
* transitively covers the entire local dependency graph.
|
|
332
|
-
*
|
|
333
|
-
* Handles all module import syntaxes:
|
|
334
|
-
* - `import ... from 'node:fs'`
|
|
335
|
-
* - `export ... from 'node:fs'`
|
|
336
|
-
* - `import fs = require('node:fs')`
|
|
337
|
-
* - `require('node:fs')`
|
|
338
|
-
* - `import('node:fs')`
|
|
339
|
-
*/
|
|
340
|
-
const collectModuleUsage = (
|
|
341
|
-
sourceFile: ts.SourceFile,
|
|
342
|
-
filePath: string,
|
|
343
|
-
violations: Violation[],
|
|
344
|
-
enqueueFile: (nextFile: string) => void,
|
|
345
|
-
) => {
|
|
346
|
-
const checkModuleSpecifier = (specifier: string, pos: number) => {
|
|
347
|
-
if (restrictedModuleSpecifiers.has(specifier)) {
|
|
348
|
-
violations.push(
|
|
349
|
-
createViolation(
|
|
350
|
-
filePath,
|
|
351
|
-
pos,
|
|
352
|
-
sourceFile,
|
|
353
|
-
`'${specifier}' is not available in CRE workflow runtime.`,
|
|
354
|
-
),
|
|
355
|
-
)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (!isRelativeImport(specifier)) return
|
|
359
|
-
const resolved = resolveRelativeImport(filePath, specifier)
|
|
360
|
-
if (resolved) {
|
|
361
|
-
enqueueFile(resolved)
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const visit = (node: ts.Node) => {
|
|
366
|
-
// import ... from 'specifier'
|
|
367
|
-
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
368
|
-
checkModuleSpecifier(node.moduleSpecifier.text, node.moduleSpecifier.getStart(sourceFile))
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// export ... from 'specifier'
|
|
372
|
-
if (
|
|
373
|
-
ts.isExportDeclaration(node) &&
|
|
374
|
-
node.moduleSpecifier &&
|
|
375
|
-
ts.isStringLiteral(node.moduleSpecifier)
|
|
376
|
-
) {
|
|
377
|
-
checkModuleSpecifier(node.moduleSpecifier.text, node.moduleSpecifier.getStart(sourceFile))
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// import fs = require('specifier')
|
|
381
|
-
if (
|
|
382
|
-
ts.isImportEqualsDeclaration(node) &&
|
|
383
|
-
ts.isExternalModuleReference(node.moduleReference) &&
|
|
384
|
-
node.moduleReference.expression &&
|
|
385
|
-
ts.isStringLiteral(node.moduleReference.expression)
|
|
386
|
-
) {
|
|
387
|
-
checkModuleSpecifier(
|
|
388
|
-
node.moduleReference.expression.text,
|
|
389
|
-
node.moduleReference.expression.getStart(sourceFile),
|
|
390
|
-
)
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (ts.isCallExpression(node)) {
|
|
394
|
-
// require('specifier')
|
|
395
|
-
if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|
|
396
|
-
const requiredModule = getStringLiteralFromCall(node)
|
|
397
|
-
if (requiredModule) {
|
|
398
|
-
checkModuleSpecifier(requiredModule, node.arguments[0].getStart(sourceFile))
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// import('specifier')
|
|
403
|
-
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
404
|
-
const importedModule = getStringLiteralFromCall(node)
|
|
405
|
-
if (importedModule) {
|
|
406
|
-
checkModuleSpecifier(importedModule, node.arguments[0].getStart(sourceFile))
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
ts.forEachChild(node, visit)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
visit(sourceFile)
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Checks whether an identifier AST node is the **name being declared** (as
|
|
419
|
-
* opposed to a reference/usage). For example, in `const fetch = ...` the
|
|
420
|
-
* `fetch` token is a declaration name, while in `fetch(url)` it is a usage.
|
|
421
|
-
*
|
|
422
|
-
* This distinction is critical so that user-defined variables that shadow
|
|
423
|
-
* restricted global names are not flagged as violations.
|
|
424
|
-
*/
|
|
425
|
-
const isDeclarationName = (identifier: ts.Identifier): boolean => {
|
|
426
|
-
const parent = identifier.parent
|
|
427
|
-
|
|
428
|
-
// Variable, function, class, interface, type alias, enum, module,
|
|
429
|
-
// type parameter, parameter, binding element, import names, enum member,
|
|
430
|
-
// property/method declarations, property assignments, and labels.
|
|
431
|
-
if (
|
|
432
|
-
(ts.isFunctionDeclaration(parent) && parent.name === identifier) ||
|
|
433
|
-
(ts.isFunctionExpression(parent) && parent.name === identifier) ||
|
|
434
|
-
(ts.isClassDeclaration(parent) && parent.name === identifier) ||
|
|
435
|
-
(ts.isClassExpression(parent) && parent.name === identifier) ||
|
|
436
|
-
(ts.isInterfaceDeclaration(parent) && parent.name === identifier) ||
|
|
437
|
-
(ts.isTypeAliasDeclaration(parent) && parent.name === identifier) ||
|
|
438
|
-
(ts.isEnumDeclaration(parent) && parent.name === identifier) ||
|
|
439
|
-
(ts.isModuleDeclaration(parent) && parent.name === identifier) ||
|
|
440
|
-
(ts.isTypeParameterDeclaration(parent) && parent.name === identifier) ||
|
|
441
|
-
(ts.isVariableDeclaration(parent) && parent.name === identifier) ||
|
|
442
|
-
(ts.isParameter(parent) && parent.name === identifier) ||
|
|
443
|
-
(ts.isBindingElement(parent) && parent.name === identifier) ||
|
|
444
|
-
(ts.isImportClause(parent) && parent.name === identifier) ||
|
|
445
|
-
(ts.isImportSpecifier(parent) && parent.name === identifier) ||
|
|
446
|
-
(ts.isNamespaceImport(parent) && parent.name === identifier) ||
|
|
447
|
-
(ts.isImportEqualsDeclaration(parent) && parent.name === identifier) ||
|
|
448
|
-
(ts.isNamespaceExport(parent) && parent.name === identifier) ||
|
|
449
|
-
(ts.isEnumMember(parent) && parent.name === identifier) ||
|
|
450
|
-
(ts.isPropertyDeclaration(parent) && parent.name === identifier) ||
|
|
451
|
-
(ts.isPropertySignature(parent) && parent.name === identifier) ||
|
|
452
|
-
(ts.isMethodDeclaration(parent) && parent.name === identifier) ||
|
|
453
|
-
(ts.isMethodSignature(parent) && parent.name === identifier) ||
|
|
454
|
-
(ts.isGetAccessorDeclaration(parent) && parent.name === identifier) ||
|
|
455
|
-
(ts.isSetAccessorDeclaration(parent) && parent.name === identifier) ||
|
|
456
|
-
(ts.isPropertyAssignment(parent) && parent.name === identifier) ||
|
|
457
|
-
(ts.isShorthandPropertyAssignment(parent) && parent.name === identifier) ||
|
|
458
|
-
(ts.isLabeledStatement(parent) && parent.label === identifier)
|
|
459
|
-
) {
|
|
460
|
-
return true
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Property access (obj.fetch), qualified names (Ns.fetch), and type
|
|
464
|
-
// references (SomeType) — the right-hand identifier is not a standalone
|
|
465
|
-
// usage of the global name.
|
|
466
|
-
if (
|
|
467
|
-
(ts.isPropertyAccessExpression(parent) && parent.name === identifier) ||
|
|
468
|
-
(ts.isQualifiedName(parent) && parent.right === identifier) ||
|
|
469
|
-
(ts.isTypeReferenceNode(parent) && parent.typeName === identifier)
|
|
470
|
-
) {
|
|
471
|
-
return true
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return false
|
|
475
|
-
}
|
|
476
|
-
|
|
477
148
|
/**
|
|
478
149
|
* **Pass 2 — Global API analysis.**
|
|
479
150
|
*
|
|
@@ -577,50 +248,27 @@ const collectGlobalApiUsage = (
|
|
|
577
248
|
*/
|
|
578
249
|
export const assertWorkflowRuntimeCompatibility = (entryFilePath: string) => {
|
|
579
250
|
const rootFile = toAbsolutePath(entryFilePath)
|
|
580
|
-
const projectCompilerOptions = loadClosestTsconfigCompilerOptions(rootFile) ?? {}
|
|
581
|
-
const filesToScan = [rootFile]
|
|
582
|
-
const scannedFiles = new Set<string>()
|
|
583
|
-
const localSourceFiles = new Set<string>()
|
|
584
251
|
const violations: Violation[] = []
|
|
585
252
|
|
|
586
253
|
// Pass 1: Walk the local import graph and collect module-level violations.
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
ts.ScriptTarget.Latest,
|
|
600
|
-
true,
|
|
601
|
-
getScriptKind(currentFile),
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
collectModuleUsage(sourceFile, currentFile, violations, (nextFile) => {
|
|
605
|
-
if (!scannedFiles.has(nextFile)) {
|
|
606
|
-
filesToScan.push(nextFile)
|
|
254
|
+
const localSourceFiles = collectLocalSourceFiles(
|
|
255
|
+
rootFile,
|
|
256
|
+
(specifier, pos, sourceFile, filePath) => {
|
|
257
|
+
if (restrictedModuleSpecifiers.has(specifier)) {
|
|
258
|
+
violations.push(
|
|
259
|
+
createViolation(
|
|
260
|
+
filePath,
|
|
261
|
+
pos,
|
|
262
|
+
sourceFile,
|
|
263
|
+
`'${specifier}' is not available in CRE workflow runtime.`,
|
|
264
|
+
),
|
|
265
|
+
)
|
|
607
266
|
}
|
|
608
|
-
})
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// Pass 2: Use the type-checker to detect restricted global API usage.
|
|
612
|
-
const program = ts.createProgram({
|
|
613
|
-
rootNames: [...localSourceFiles],
|
|
614
|
-
options: {
|
|
615
|
-
...defaultValidationCompilerOptions,
|
|
616
|
-
...projectCompilerOptions,
|
|
617
|
-
allowJs: true,
|
|
618
|
-
checkJs: true,
|
|
619
|
-
noEmit: true,
|
|
620
|
-
skipLibCheck: true,
|
|
621
267
|
},
|
|
622
|
-
|
|
268
|
+
)
|
|
623
269
|
|
|
270
|
+
// Pass 2: Use the type-checker to detect restricted global API usage.
|
|
271
|
+
const program = createValidationProgram(rootFile, localSourceFiles)
|
|
624
272
|
collectGlobalApiUsage(program, localSourceFiles, violations)
|
|
625
273
|
|
|
626
274
|
if (violations.length > 0) {
|