Skip to content

Commit

Permalink
Outline for DOM schema extension
Browse files Browse the repository at this point in the history
  • Loading branch information
sijakret committed Oct 8, 2021
1 parent 9d290bd commit 2339fe2
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 14 deletions.
15 changes: 15 additions & 0 deletions packages/compiler-cli/src/ngtsc/core/api/src/public_options.ts
Expand Up @@ -388,4 +388,19 @@ export interface MiscOptions {
* Disable TypeScript Version Check.
*/
disableTypeScriptVersionCheck?: boolean;

/**
* HTML DOM Schema extension point.
* Superior alternative to CUSTOM_ELEMENTS_SCHEMA since it allows
* to define custom elements and their properties.
* Example:
* customSchema: [
* 'my-element^[HTMLElement]|string-prop,#number-prop,!boolean-prop,myprop,..',
* 'my-other-element^[HTMLElement]|prop,..',
* ..
* ]
*
* For reference see: packages\compiler\src\schema\dom_element_schema_registry.ts
*/
customSchema?: string[];
}
4 changes: 4 additions & 0 deletions packages/compiler-cli/src/ngtsc/core/src/compiler.ts
Expand Up @@ -769,6 +769,8 @@ export class NgCompiler {
const strictTemplates = !!this.options.strictTemplates;

const useInlineTypeConstructors = this.programDriver.supportsInlineOperations;
// by default anything with a dash could be a web component
const customSchema = this.options.customSchema || [];

// First select a type-checking configuration, based on whether full template type-checking is
// requested.
Expand Down Expand Up @@ -806,6 +808,7 @@ export class NgCompiler {
// (providing the full TemplateTypeChecker API) and if strict mode is not enabled. In strict
// mode, the user is in full control of type inference.
suggestionsForSuboptimalTypeInference: this.enableTemplateTypeChecker && !strictTemplates,
customSchema,
};
} else {
typeCheckingConfig = {
Expand Down Expand Up @@ -834,6 +837,7 @@ export class NgCompiler {
// In "basic" template type-checking mode, no warnings are produced since most things are
// not checked anyways.
suggestionsForSuboptimalTypeInference: false,
customSchema,
};
}

Expand Down
15 changes: 15 additions & 0 deletions packages/compiler-cli/src/ngtsc/typecheck/api/api.ts
Expand Up @@ -311,6 +311,21 @@ export interface TypeCheckingConfig {
* opportunities to improve their own developer experience.
*/
suggestionsForSuboptimalTypeInference: boolean;

/**
* HTML DOM Schema extension point.
* Superior alternative to CUSTOM_ELEMENTS_SCHEMA since it allows
* to define custom elements and their properties.
* Example:
* customSchema: [
* 'my-element^[HTMLElement]|string-prop,#number-prop,!boolean-prop,myprop,..',
* 'my-other-element^[HTMLElement]|prop,..',
* ..
* ]
*
* For reference see: packages\compiler\src\schema\dom_element_schema_registry.ts
*/
customSchema: string[];
}


Expand Down
13 changes: 8 additions & 5 deletions packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts
Expand Up @@ -32,7 +32,6 @@ import {findTypeCheckBlock, getTemplateMapping, TemplateSourceResolver} from './
import {SymbolBuilder} from './template_symbol_builder';


const REGISTRY = new DomElementSchemaRegistry();
/**
* Primary template type-checking engine, which performs type-checking using a
* `TypeCheckingProgramStrategy` for type-checking program maintenance, and the
Expand Down Expand Up @@ -87,7 +86,11 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>,
private readonly componentScopeReader: ComponentScopeReader,
private readonly typeCheckScopeRegistry: TypeCheckScopeRegistry,
private readonly perf: PerfRecorder) {}
private readonly perf: PerfRecorder,
// initialize registry late (once TypeCheckingConfig is ready)
private _registry = new DomElementSchemaRegistry(config.customSchema)) {

}

getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null {
const {data} = this.getLatestComponentState(component);
Expand Down Expand Up @@ -551,7 +554,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {

const tagMap = new Map<string, DirectiveInScope|null>();

for (const tag of REGISTRY.allKnownElementNames()) {
for (const tag of this._registry.allKnownElementNames()) {
tagMap.set(tag, null);
}

Expand All @@ -575,10 +578,10 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
}

getPotentialDomBindings(tagName: string): {attribute: string, property: string}[] {
const attributes = REGISTRY.allKnownAttributesOfElement(tagName);
const attributes = this._registry.allKnownAttributesOfElement(tagName);
return attributes.map(attribute => ({
attribute,
property: REGISTRY.getMappedPropName(attribute),
property: this._registry.getMappedPropName(attribute),
}));
}

Expand Down
4 changes: 3 additions & 1 deletion packages/compiler-cli/src/ngtsc/typecheck/src/context.ts
Expand Up @@ -436,7 +436,9 @@ export class TypeCheckContextImpl implements TypeCheckContext {
const shimPath = TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(node.getSourceFile()));
if (!fileData.shimData.has(shimPath)) {
fileData.shimData.set(shimPath, {
domSchemaChecker: new RegistryDomSchemaChecker(fileData.sourceManager),
// by passing in this.config we allow access to customSchema so
// RegistryDomSchemaChecker can be aware of it
domSchemaChecker: new RegistryDomSchemaChecker(fileData.sourceManager, this.config),
oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager),
file: new TypeCheckFile(
shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost),
Expand Down
19 changes: 13 additions & 6 deletions packages/compiler-cli/src/ngtsc/typecheck/src/dom.ts
Expand Up @@ -6,16 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/

import {DomElementSchemaRegistry, ParseSourceSpan, SchemaMetadata, TmplAstElement} from '@angular/compiler';
import {CompilerConfig, DomElementSchemaRegistry, ParseSourceSpan, SchemaMetadata, TmplAstElement} from '@angular/compiler';
import * as ts from 'typescript';

import {ErrorCode, ngErrorCode} from '../../diagnostics';
import {TemplateDiagnostic, TemplateId} from '../api';
import {TemplateDiagnostic, TemplateId, TypeCheckingConfig} from '../api';
import {makeTemplateDiagnostic} from '../diagnostics';

import {TemplateSourceResolver} from './tcb_util';

const REGISTRY = new DomElementSchemaRegistry();
const REMOVE_XHTML_REGEX = /^:xhtml:/;

/**
Expand Down Expand Up @@ -66,20 +65,28 @@ export interface DomSchemaChecker {
*/
export class RegistryDomSchemaChecker implements DomSchemaChecker {
private _diagnostics: TemplateDiagnostic[] = [];
private _registry: DomElementSchemaRegistry

get diagnostics(): ReadonlyArray<TemplateDiagnostic> {
return this._diagnostics;
}

constructor(private resolver: TemplateSourceResolver) {}
constructor(
private resolver: TemplateSourceResolver,
typeCheckingConfig: TypeCheckingConfig,
) {
// Allow registry to be initialized late (once config is ready)
this._registry =
new DomElementSchemaRegistry(typeCheckingConfig.customSchema)
}

checkElement(id: TemplateId, element: TmplAstElement, schemas: SchemaMetadata[]): void {
// HTML elements inside an SVG `foreignObject` are declared in the `xhtml` namespace.
// We need to strip it before handing it over to the registry because all HTML tag names
// in the registry are without a namespace.
const name = element.name.replace(REMOVE_XHTML_REGEX, '');

if (!REGISTRY.hasElement(name, schemas)) {
if (!this._registry.hasElement(name, schemas)) {
const mapping = this.resolver.getSourceMapping(id);

let errorMsg = `'${name}' is not a known element:\n`;
Expand All @@ -103,7 +110,7 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
checkProperty(
id: TemplateId, element: TmplAstElement, name: string, span: ParseSourceSpan,
schemas: SchemaMetadata[]): void {
if (!REGISTRY.hasProperty(element.name, name, schemas)) {
if (!this._registry.hasProperty(element.name, name, schemas)) {
const mapping = this.resolver.getSourceMapping(id);

let errorMsg =
Expand Down
6 changes: 4 additions & 2 deletions packages/compiler/src/schema/dom_element_schema_registry.ts
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {CompilerConfig} from '../config';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '../core';

import {isNgContainer, isNgContent} from '../ml_parser/tags';
Expand Down Expand Up @@ -250,9 +251,10 @@ const _PROP_TO_ATTR: {[name: string]: string} =
export class DomElementSchemaRegistry extends ElementSchemaRegistry {
private _schema: {[element: string]: {[property: string]: string}} = {};

constructor() {
// optional customSchema can be used to extend built-in HTML schema
constructor(customSchema: string[] = []) {
super();
SCHEMA.forEach(encodedType => {
[...SCHEMA,...customSchema].forEach(encodedType => {
const type: {[property: string]: string} = {};
const [strType, strProperties] = encodedType.split('|');
const properties = strProperties.split(',');
Expand Down

0 comments on commit 2339fe2

Please sign in to comment.