feat(ivy): introduce type-checking context API (#26203)
This commit introduces the template type-checking context API, which manages inlining of type constructors and type-check blocks into ts.SourceFiles. This API will be used by ngtsc to generate a type-checking ts.Program. An TypeCheckProgramHost is provided which can wrap a normal ts.CompilerHost and intercept getSourceFile() calls. This can be used to provide source files with type check blocks to a ts.Program for type-checking. PR Close #26203
This commit is contained in:
parent
355a7cae3c
commit
5f1273ba2e
|
@ -10,10 +10,10 @@ import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
export function makeProgram(
|
export function makeProgram(
|
||||||
files: {name: string, contents: string}[],
|
files: {name: string, contents: string}[], options?: ts.CompilerOptions,
|
||||||
options?: ts.CompilerOptions): {program: ts.Program, host: ts.CompilerHost} {
|
host: ts.CompilerHost = new InMemoryHost(),
|
||||||
const host = new InMemoryHost();
|
checkForErrors: boolean = true): {program: ts.Program, host: ts.CompilerHost} {
|
||||||
files.forEach(file => host.writeFile(file.name, file.contents));
|
files.forEach(file => host.writeFile(file.name, file.contents, false, undefined, []));
|
||||||
|
|
||||||
const rootNames = files.map(file => host.getCanonicalFileName(file.name));
|
const rootNames = files.map(file => host.getCanonicalFileName(file.name));
|
||||||
const program = ts.createProgram(
|
const program = ts.createProgram(
|
||||||
|
@ -23,17 +23,20 @@ export function makeProgram(
|
||||||
moduleResolution: ts.ModuleResolutionKind.NodeJs, ...options
|
moduleResolution: ts.ModuleResolutionKind.NodeJs, ...options
|
||||||
},
|
},
|
||||||
host);
|
host);
|
||||||
const diags = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()];
|
if (checkForErrors) {
|
||||||
if (diags.length > 0) {
|
const diags = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()];
|
||||||
const errors = diags.map(diagnostic => {
|
if (diags.length > 0) {
|
||||||
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
const errors = diags.map(diagnostic => {
|
||||||
if (diagnostic.file) {
|
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
||||||
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start !);
|
if (diagnostic.file) {
|
||||||
message = `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`;
|
const {line, character} =
|
||||||
}
|
diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start !);
|
||||||
return `Error: ${message}`;
|
message = `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`;
|
||||||
});
|
}
|
||||||
throw new Error(`Typescript diagnostics failed! ${errors.join(', ')}`);
|
return `Error: ${message}`;
|
||||||
|
});
|
||||||
|
throw new Error(`Typescript diagnostics failed! ${errors.join(', ')}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {program, host};
|
return {program, host};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './src/api';
|
||||||
|
export {TypeCheckContext} from './src/context';
|
||||||
|
export {TypeCheckProgramHost} from './src/host';
|
|
@ -0,0 +1,240 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {R3TargetBinder, SelectorMatcher, TmplAstNode} from '@angular/compiler';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ImportManager} from '../../translator';
|
||||||
|
|
||||||
|
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCtorMetadata} from './api';
|
||||||
|
import {generateTypeCheckBlock} from './type_check_block';
|
||||||
|
import {generateTypeCtor} from './type_constructor';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A template type checking context for a program.
|
||||||
|
*
|
||||||
|
* The `TypeCheckContext` allows registration of components and their templates which need to be
|
||||||
|
* type checked. It also allows generation of modified `ts.SourceFile`s which contain the type
|
||||||
|
* checking code.
|
||||||
|
*/
|
||||||
|
export class TypeCheckContext {
|
||||||
|
/**
|
||||||
|
* A `Set` of classes which will be used to generate type constructors.
|
||||||
|
*/
|
||||||
|
private typeCtors = new Set<ts.ClassDeclaration>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `Map` of `ts.SourceFile`s that the context has seen to the operations (additions of methods
|
||||||
|
* or type-check blocks) that need to be eventually performed on that file.
|
||||||
|
*/
|
||||||
|
private opMap = new Map<ts.SourceFile, Op[]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record a template for the given component `node`, with a `SelectorMatcher` for directive
|
||||||
|
* matching.
|
||||||
|
*
|
||||||
|
* @param node class of the node being recorded.
|
||||||
|
* @param template AST nodes of the template being recorded.
|
||||||
|
* @param matcher `SelectorMatcher` which tracks directives that are in scope for this template.
|
||||||
|
*/
|
||||||
|
addTemplate(
|
||||||
|
node: ts.ClassDeclaration, template: TmplAstNode[],
|
||||||
|
matcher: SelectorMatcher<TypeCheckableDirectiveMeta>): void {
|
||||||
|
// Only write TCBs for named classes.
|
||||||
|
if (node.name === undefined) {
|
||||||
|
throw new Error(`Assertion: class must be named`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind the template, which will:
|
||||||
|
// - Extract the metadata needed to generate type check blocks.
|
||||||
|
// - Perform directive matching, which informs the context which directives are used in the
|
||||||
|
// template. This allows generation of type constructors for only those directives which
|
||||||
|
// are actually used by the templates.
|
||||||
|
const binder = new R3TargetBinder(matcher);
|
||||||
|
const boundTarget = binder.bind({template});
|
||||||
|
|
||||||
|
// Get all of the directives used in the template and record type constructors for all of them.
|
||||||
|
boundTarget.getUsedDirectives().forEach(dir => {
|
||||||
|
const dirNode = dir.ref.node;
|
||||||
|
// Add a type constructor operation for the directive.
|
||||||
|
this.addTypeCtor(dirNode.getSourceFile(), dirNode, {
|
||||||
|
fnName: 'ngTypeCtor',
|
||||||
|
// The constructor should have a body if the directive comes from a .ts file, but not if it
|
||||||
|
// comes from a .d.ts file. .d.ts declarations don't have bodies.
|
||||||
|
body: !dirNode.getSourceFile().fileName.endsWith('.d.ts'),
|
||||||
|
fields: {
|
||||||
|
inputs: Object.keys(dir.inputs),
|
||||||
|
outputs: Object.keys(dir.outputs),
|
||||||
|
// TODO: support queries
|
||||||
|
queries: dir.queries,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Record the type check block operation for the template itself.
|
||||||
|
this.addTypeCheckBlock(node.getSourceFile(), node, {
|
||||||
|
boundTarget,
|
||||||
|
fnName: `${node.name.text}_TypeCheckBlock`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record a type constructor for the given `node` with the given `ctorMetadata`.
|
||||||
|
*/
|
||||||
|
addTypeCtor(sf: ts.SourceFile, node: ts.ClassDeclaration, ctorMeta: TypeCtorMetadata): void {
|
||||||
|
if (this.hasTypeCtor(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Lazily construct the operation map.
|
||||||
|
if (!this.opMap.has(sf)) {
|
||||||
|
this.opMap.set(sf, []);
|
||||||
|
}
|
||||||
|
const ops = this.opMap.get(sf) !;
|
||||||
|
|
||||||
|
// Push a `TypeCtorOp` into the operation queue for the source file.
|
||||||
|
ops.push(new TypeCtorOp(node, ctorMeta));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a `ts.SourceFile` into a version that includes type checking code.
|
||||||
|
*
|
||||||
|
* If this particular source file has no directives that require type constructors, or components
|
||||||
|
* that require type check blocks, then it will be returned directly. Otherwise, a new
|
||||||
|
* `ts.SourceFile` is parsed from modified text of the original. This is necessary to ensure the
|
||||||
|
* added code has correct positional information associated with it.
|
||||||
|
*/
|
||||||
|
transform(sf: ts.SourceFile): ts.SourceFile {
|
||||||
|
// If there are no operations pending for this particular file, return it directly.
|
||||||
|
if (!this.opMap.has(sf)) {
|
||||||
|
return sf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Imports may need to be added to the file to support type-checking of directives used in the
|
||||||
|
// template within it.
|
||||||
|
const importManager = new ImportManager(false, '_i');
|
||||||
|
|
||||||
|
// Each Op has a splitPoint index into the text where it needs to be inserted. Split the
|
||||||
|
// original source text into chunks at these split points, where code will be inserted between
|
||||||
|
// the chunks.
|
||||||
|
const ops = this.opMap.get(sf) !.sort(orderOps);
|
||||||
|
const textParts = splitStringAtPoints(sf.text, ops.map(op => op.splitPoint));
|
||||||
|
|
||||||
|
// Use a `ts.Printer` to generate source code.
|
||||||
|
const printer = ts.createPrinter({omitTrailingSemicolon: true});
|
||||||
|
|
||||||
|
// Begin with the intial section of the code text.
|
||||||
|
let code = textParts[0];
|
||||||
|
|
||||||
|
// Process each operation and use the printer to generate source code for it, inserting it into
|
||||||
|
// the source code in between the original chunks.
|
||||||
|
ops.forEach((op, idx) => {
|
||||||
|
const text = op.execute(importManager, sf, printer);
|
||||||
|
code += text + textParts[idx + 1];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write out the imports that need to be added to the beginning of the file.
|
||||||
|
let imports = importManager.getAllImports(sf.fileName, null)
|
||||||
|
.map(i => `import * as ${i.as} from '${i.name}';`)
|
||||||
|
.join('\n');
|
||||||
|
code = imports + '\n' + code;
|
||||||
|
|
||||||
|
// Parse the new source file and return it.
|
||||||
|
return ts.createSourceFile(sf.fileName, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the given `node` has a type constructor already.
|
||||||
|
*/
|
||||||
|
private hasTypeCtor(node: ts.ClassDeclaration): boolean { return this.typeCtors.has(node); }
|
||||||
|
|
||||||
|
private addTypeCheckBlock(
|
||||||
|
sf: ts.SourceFile, node: ts.ClassDeclaration, tcbMeta: TypeCheckBlockMetadata): void {
|
||||||
|
if (!this.opMap.has(sf)) {
|
||||||
|
this.opMap.set(sf, []);
|
||||||
|
}
|
||||||
|
const ops = this.opMap.get(sf) !;
|
||||||
|
ops.push(new TcbOp(node, tcbMeta));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A code generation operation that needs to happen within a given source file.
|
||||||
|
*/
|
||||||
|
interface Op {
|
||||||
|
/**
|
||||||
|
* The node in the file which will have code generated for it.
|
||||||
|
*/
|
||||||
|
readonly node: ts.ClassDeclaration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index into the source text where the code generated by the operation should be inserted.
|
||||||
|
*/
|
||||||
|
readonly splitPoint: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the operation and return the generated code as text.
|
||||||
|
*/
|
||||||
|
execute(im: ImportManager, sf: ts.SourceFile, printer: ts.Printer): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type check block operation which produces type check code for a particular component.
|
||||||
|
*/
|
||||||
|
class TcbOp implements Op {
|
||||||
|
constructor(readonly node: ts.ClassDeclaration, readonly meta: TypeCheckBlockMetadata) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type check blocks are inserted immediately after the end of the component class.
|
||||||
|
*/
|
||||||
|
get splitPoint(): number { return this.node.end + 1; }
|
||||||
|
|
||||||
|
execute(im: ImportManager, sf: ts.SourceFile, printer: ts.Printer): string {
|
||||||
|
const tcb = generateTypeCheckBlock(this.node, this.meta, im);
|
||||||
|
return printer.printNode(ts.EmitHint.Unspecified, tcb, sf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type constructor operation which produces type constructor code for a particular directive.
|
||||||
|
*/
|
||||||
|
class TypeCtorOp implements Op {
|
||||||
|
constructor(readonly node: ts.ClassDeclaration, readonly meta: TypeCtorMetadata) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type constructor operations are inserted immediately before the end of the directive class.
|
||||||
|
*/
|
||||||
|
get splitPoint(): number { return this.node.end - 1; }
|
||||||
|
|
||||||
|
execute(im: ImportManager, sf: ts.SourceFile, printer: ts.Printer): string {
|
||||||
|
const tcb = generateTypeCtor(this.node, this.meta);
|
||||||
|
return printer.printNode(ts.EmitHint.Unspecified, tcb, sf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two operations and return their split point ordering.
|
||||||
|
*/
|
||||||
|
function orderOps(op1: Op, op2: Op): number {
|
||||||
|
return op1.splitPoint - op2.splitPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split a string into chunks at any number of split points.
|
||||||
|
*/
|
||||||
|
function splitStringAtPoints(str: string, points: number[]): string[] {
|
||||||
|
const splits: string[] = [];
|
||||||
|
let start = 0;
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
const point = points[i];
|
||||||
|
splits.push(str.substring(start, point));
|
||||||
|
start = point;
|
||||||
|
}
|
||||||
|
splits.push(str.substring(start));
|
||||||
|
return splits;
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import {TypeCheckContext} from './context';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `ts.CompilerHost` which augments source files with type checking code from a
|
||||||
|
* `TypeCheckContext`.
|
||||||
|
*/
|
||||||
|
export class TypeCheckProgramHost implements ts.CompilerHost {
|
||||||
|
/**
|
||||||
|
* Map of source file names to `ts.SourceFile` instances.
|
||||||
|
*
|
||||||
|
* This is prepopulated with all the old source files, and updated as files are augmented.
|
||||||
|
*/
|
||||||
|
private sfCache = new Map<string, ts.SourceFile>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks those files in `sfCache` which have been augmented with type checking information
|
||||||
|
* already.
|
||||||
|
*/
|
||||||
|
private augmentedSourceFiles = new Set<ts.SourceFile>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
program: ts.Program, private delegate: ts.CompilerHost, private context: TypeCheckContext) {
|
||||||
|
// The `TypeCheckContext` uses object identity for `ts.SourceFile`s to track which files need
|
||||||
|
// type checking code inserted. Additionally, the operation of getting a source file should be
|
||||||
|
// as efficient as possible. To support both of these requirements, all of the program's
|
||||||
|
// source files are loaded into the cache up front.
|
||||||
|
program.getSourceFiles().forEach(file => { this.sfCache.set(file.fileName, file); });
|
||||||
|
}
|
||||||
|
|
||||||
|
getSourceFile(
|
||||||
|
fileName: string, languageVersion: ts.ScriptTarget,
|
||||||
|
onError?: ((message: string) => void)|undefined,
|
||||||
|
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
||||||
|
// Look in the cache for the source file.
|
||||||
|
let sf: ts.SourceFile|undefined = this.sfCache.get(fileName);
|
||||||
|
if (sf === undefined) {
|
||||||
|
// There should be no cache misses, but just in case, delegate getSourceFile in the event of
|
||||||
|
// a cache miss.
|
||||||
|
sf = this.delegate.getSourceFile(
|
||||||
|
fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||||
|
sf && this.sfCache.set(fileName, sf);
|
||||||
|
}
|
||||||
|
if (sf !== undefined) {
|
||||||
|
// Maybe augment the file with type checking code via the `TypeCheckContext`.
|
||||||
|
if (!this.augmentedSourceFiles.has(sf)) {
|
||||||
|
sf = this.context.transform(sf);
|
||||||
|
this.sfCache.set(fileName, sf);
|
||||||
|
this.augmentedSourceFiles.add(sf);
|
||||||
|
}
|
||||||
|
return sf;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest of the methods simply delegate to the underlying `ts.CompilerHost`.
|
||||||
|
|
||||||
|
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||||
|
return this.delegate.getDefaultLibFileName(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile(
|
||||||
|
fileName: string, data: string, writeByteOrderMark: boolean,
|
||||||
|
onError: ((message: string) => void)|undefined,
|
||||||
|
sourceFiles: ReadonlyArray<ts.SourceFile>): void {
|
||||||
|
return this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentDirectory(): string { return this.delegate.getCurrentDirectory(); }
|
||||||
|
|
||||||
|
getDirectories(path: string): string[] { return this.delegate.getDirectories(path); }
|
||||||
|
|
||||||
|
getCanonicalFileName(fileName: string): string {
|
||||||
|
return this.delegate.getCanonicalFileName(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
useCaseSensitiveFileNames(): boolean { return this.delegate.useCaseSensitiveFileNames(); }
|
||||||
|
|
||||||
|
getNewLine(): string { return this.delegate.getNewLine(); }
|
||||||
|
|
||||||
|
fileExists(fileName: string): boolean { return this.delegate.fileExists(fileName); }
|
||||||
|
|
||||||
|
readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); }
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "test_lib",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = glob([
|
||||||
|
"**/*.ts",
|
||||||
|
]),
|
||||||
|
deps = [
|
||||||
|
"//packages:types",
|
||||||
|
"//packages/compiler",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/host",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/testing",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
jasmine_node_test(
|
||||||
|
name = "test",
|
||||||
|
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
|
||||||
|
deps = [
|
||||||
|
":test_lib",
|
||||||
|
"//tools/testing:node_no_angular",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||||
|
import {TypeCheckContext} from '../src/context';
|
||||||
|
import {TypeCheckProgramHost} from '../src/host';
|
||||||
|
|
||||||
|
const LIB_D_TS = {
|
||||||
|
name: 'lib.d.ts',
|
||||||
|
contents: `
|
||||||
|
type Partial<T> = { [P in keyof T]?: T[P]; };
|
||||||
|
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
|
||||||
|
type NonNullable<T> = T extends null | undefined ? never : T;`
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ngtsc typechecking', () => {
|
||||||
|
describe('ctors', () => {
|
||||||
|
it('compiles a basic type constructor', () => {
|
||||||
|
const ctx = new TypeCheckContext();
|
||||||
|
const files = [
|
||||||
|
LIB_D_TS, {
|
||||||
|
name: 'main.ts',
|
||||||
|
contents: `
|
||||||
|
class TestClass<T extends string> {
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestClass.ngTypeCtor({value: 'test'});
|
||||||
|
`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const {program, host} = makeProgram(files, undefined, undefined, false);
|
||||||
|
const TestClass = getDeclaration(program, 'main.ts', 'TestClass', ts.isClassDeclaration);
|
||||||
|
ctx.addTypeCtor(program.getSourceFile('main.ts') !, TestClass, {
|
||||||
|
fnName: 'ngTypeCtor',
|
||||||
|
body: true,
|
||||||
|
fields: {
|
||||||
|
inputs: ['value'],
|
||||||
|
outputs: [],
|
||||||
|
queries: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const augHost = new TypeCheckProgramHost(program, host, ctx);
|
||||||
|
makeProgram(files, undefined, augHost, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue