Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(ivy): flatten css selectors #23074

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules/benchmarks/src/largetable/render3/table.ts
Expand Up @@ -17,7 +17,7 @@ export class LargeTableComponent {
/** @nocollapse */
static ngComponentDef: ComponentDef<LargeTableComponent> = defineComponent({
type: LargeTableComponent,
selector: [[['largetable'], null]],
selectors: [['largetable']],
template: function(ctx: LargeTableComponent, cm: boolean) {
if (cm) {
E(0, 'table');
Expand Down
4 changes: 2 additions & 2 deletions modules/benchmarks/src/tree/render3/tree.ts
Expand Up @@ -37,7 +37,7 @@ export class TreeComponent {
/** @nocollapse */
static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({
type: TreeComponent,
selector: [[['tree'], null]],
selectors: [['tree']],
template: function(ctx: TreeComponent, cm: boolean) {
if (cm) {
E(0, 'span');
Expand Down Expand Up @@ -91,7 +91,7 @@ export class TreeFunction {
/** @nocollapse */
static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({
type: TreeFunction,
selector: [[['tree'], null]],
selectors: [['tree']],
template: function(ctx: TreeFunction, cm: boolean) {
// bit of a hack
TreeTpl(ctx.data, cm);
Expand Down
79 changes: 59 additions & 20 deletions packages/compiler/src/render3/r3_view_compiler.ts
Expand Up @@ -60,8 +60,8 @@ export function compileDirective(
// e.g. 'type: MyDirective`
field('type', outputCtx.importExpr(directive.type.reference));

// e.g. `selector: [[[null, 'someDir', ''], null]]`
field('selector', createDirectiveSelector(directive.selector !));
// e.g. `selectors: [['', 'someDir', '']]`
field('selectors', createDirectiveSelector(directive.selector !));

// e.g. `factory: () => new MyApp(injectElementRef())`
field('factory', createFactory(directive.type, outputCtx, reflector, directive.queries));
Expand Down Expand Up @@ -121,8 +121,8 @@ export function compileComponent(
// e.g. `type: MyApp`
field('type', outputCtx.importExpr(component.type.reference));

// e.g. `selector: [[['my-app'], null]]`
field('selector', createDirectiveSelector(component.selector !));
// e.g. `selectors: [['my-app']]`
field('selectors', createDirectiveSelector(component.selector !));

const selector = component.selector && CssSelector.parse(component.selector);
const firstSelector = selector && selector[0];
Expand Down Expand Up @@ -387,7 +387,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
const contentProjections = getContentProjection(asts, this.ngContentSelectors);
this._contentProjections = contentProjections;
if (contentProjections.size > 0) {
const infos: R3CssSelector[] = [];
const infos: R3CssSelectorList[] = [];
Array.from(contentProjections.values()).forEach(info => {
if (info.selector) {
infos[info.index - 1] = info.selector;
Expand Down Expand Up @@ -1097,7 +1097,7 @@ function findComponent(directives: DirectiveAst[]): DirectiveAst|undefined {

interface NgContentInfo {
index: number;
selector?: R3CssSelector;
selector?: R3CssSelectorList;
}

class ContentProjectionVisitor extends RecursiveTemplateAstVisitor {
Expand Down Expand Up @@ -1128,28 +1128,67 @@ function getContentProjection(asts: TemplateAst[], ngContentSelectors: string[])
return projectIndexMap;
}


/**
* Flags used to generate R3-style CSS Selectors. They are pasted from
* core/src/render3/projection.ts because they cannot be referenced directly.
*/
const enum SelectorFlags {
/** Indicates this is the beginning of a new negative selector */
NOT = 0b0001,

/** Mode for matching attributes */
ATTRIBUTE = 0b0010,

/** Mode for matching tag names */
ELEMENT = 0b0100,

/** Mode for matching class names */
CLASS = 0b1000,
}

// These are a copy the CSS types from core/src/render3/interfaces/projection.ts
// They are duplicated here as they cannot be directly referenced from core.
type R3SimpleCssSelector = (string | null)[];
type R3CssSelectorWithNegations =
[R3SimpleCssSelector, null] | [R3SimpleCssSelector, R3SimpleCssSelector];
type R3CssSelector = R3CssSelectorWithNegations[];

function parserSelectorToSimpleSelector(selector: CssSelector): R3SimpleCssSelector {
const classes =
selector.classNames && selector.classNames.length ? ['class', ...selector.classNames] : [];
return [selector.element, ...selector.attrs, ...classes];
type R3CssSelector = (string | SelectorFlags)[];
type R3CssSelectorList = R3CssSelector[];

function parserSelectorToSimpleSelector(selector: CssSelector): R3CssSelector {
const classes = selector.classNames && selector.classNames.length ?
[SelectorFlags.CLASS, ...selector.classNames] :
[];
const elementName = selector.element && selector.element !== '*' ? selector.element : '';
return [elementName, ...selector.attrs, ...classes];
}

function parserSelectorToR3Selector(selector: CssSelector): R3CssSelectorWithNegations {
function parserSelectorToNegativeSelector(selector: CssSelector): R3CssSelector {
const classes = selector.classNames && selector.classNames.length ?
[SelectorFlags.CLASS, ...selector.classNames] :
[];

if (selector.element) {
return [
SelectorFlags.NOT | SelectorFlags.ELEMENT, selector.element, ...selector.attrs, ...classes
];
} else if (selector.attrs.length) {
return [SelectorFlags.NOT | SelectorFlags.ATTRIBUTE, ...selector.attrs, ...classes];
} else {
return selector.classNames && selector.classNames.length ?
[SelectorFlags.NOT | SelectorFlags.CLASS, ...selector.classNames] :
[];
}
}

function parserSelectorToR3Selector(selector: CssSelector): R3CssSelector {
const positive = parserSelectorToSimpleSelector(selector);
const negative = selector.notSelectors && selector.notSelectors.length &&
parserSelectorToSimpleSelector(selector.notSelectors[0]);

return negative ? [positive, negative] : [positive, null];
const negative: R3CssSelectorList = selector.notSelectors && selector.notSelectors.length ?
selector.notSelectors.map(notSelector => parserSelectorToNegativeSelector(notSelector)) :
[];

return positive.concat(...negative);
}

function parseSelectorsToR3Selector(selectors: CssSelector[]): R3CssSelector {
function parseSelectorsToR3Selector(selectors: CssSelector[]): R3CssSelectorList {
return selectors.map(parserSelectorToR3Selector);
}

Expand Down
87 changes: 65 additions & 22 deletions packages/compiler/test/render3/r3_compiler_compliance_spec.ts
Expand Up @@ -92,7 +92,7 @@ describe('compiler compliance', () => {
const ChildComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: ChildComponent,
selector: [[['child'], null]],
selectors: [['child']],
factory: function ChildComponent_Factory() { return new ChildComponent(); },
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand All @@ -106,7 +106,7 @@ describe('compiler compliance', () => {
const SomeDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: SomeDirective,
selector: [[[null, 'some-directive', ''], null]],
selectors: [['', 'some-directive', '']],
factory: function SomeDirective_Factory() {return new SomeDirective(); }
});
`;
Expand All @@ -118,7 +118,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
selector: [[['my-component'], null]],
selectors: [['my-component']],
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand All @@ -143,6 +143,49 @@ describe('compiler compliance', () => {
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
});

it('should support complex selectors', () => {
const files = {
app: {
'spec.ts': `
import {Directive, NgModule} from '@angular/core';

@Directive({selector: 'div.foo[some-directive]:not([title]):not(.baz)'})
export class SomeDirective {}

@Directive({selector: ':not(span[title]):not(.baz)'})
export class OtherDirective {}

@NgModule({declarations: [SomeDirective, OtherDirective]})
export class MyModule{}
`
}
};

// SomeDirective definition should be:
const SomeDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: SomeDirective,
selectors: [['div', 'some-directive', '', 8, 'foo', 3, 'title', '', 9, 'baz']],
factory: function SomeDirective_Factory() {return new SomeDirective(); }
});
`;

// OtherDirective definition should be:
const OtherDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: OtherDirective,
selectors: [['', 5, 'span', 'title', '', 9, 'baz']],
factory: function OtherDirective_Factory() {return new OtherDirective(); }
});
`;

const result = compile(files, angularFiles);
const source = result.source;

expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ngDirectiveDef');
});

it('should support host bindings', () => {
const files = {
app: {
Expand All @@ -163,7 +206,7 @@ describe('compiler compliance', () => {
const HostBindingDirDeclaration = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: HostBindingDir,
selector: [[[null, 'hostBindingDir', ''], null]],
selectors: [['', 'hostBindingDir', '']],
factory: function HostBindingDir_Factory() { return new HostBindingDir(); },
hostBindings: function HostBindingDir_HostBindings(
dirIndex: $number$, elIndex: $number$) {
Expand Down Expand Up @@ -206,7 +249,7 @@ describe('compiler compliance', () => {
const IfDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: IfDirective,
selector: [[[null, 'if', ''], null]],
selectors: [['', 'if', '']],
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
});`;
const MyComponentDefinition = `
Expand All @@ -215,7 +258,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
selector: [[['my-component'], null]],
selectors: [['my-component']],
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand Down Expand Up @@ -287,7 +330,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyApp,
selector: [[['my-app'], null]],
selectors: [['my-app']],
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
if (cm) {
Expand Down Expand Up @@ -365,7 +408,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyApp,
selector: [[['my-app'], null]],
selectors: [['my-app']],
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
if (cm) {
Expand Down Expand Up @@ -425,7 +468,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyApp,
selector: [[['my-app'], null]],
selectors: [['my-app']],
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
if (cm) {
Expand Down Expand Up @@ -489,7 +532,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyApp,
selector: [[['my-app'], null]],
selectors: [['my-app']],
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
if (cm) {
Expand Down Expand Up @@ -546,7 +589,7 @@ describe('compiler compliance', () => {
const SimpleComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleComponent,
selector: [[['simple'], null]],
selectors: [['simple']],
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand All @@ -559,13 +602,13 @@ describe('compiler compliance', () => {
});`;

const ComplexComponentDefinition = `
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
const $c1$ = [[['span', 'title', 'tofirst']], [['span', 'title', 'tosecond']]];
const $c2$ = ['id','first'];
const $c3$ = ['id','second'];
static ngComponentDef = $r3$.ɵdefineComponent({
type: ComplexComponent,
selector: [[['complex'], null]],
selectors: [['complex']],
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand Down Expand Up @@ -631,7 +674,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: ViewQueryComponent,
selector: [[['view-query-component'], null]],
selectors: [['view-query-component']],
factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); },
template: function ViewQueryComponent_Template(ctx: $ViewQueryComponent$, cm: $boolean$) {
var $tmp$: $any$;
Expand Down Expand Up @@ -689,7 +732,7 @@ describe('compiler compliance', () => {
const ContentQueryComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: ContentQueryComponent,
selector: [[['content-query-component'], null]],
selectors: [['content-query-component']],
factory: function ContentQueryComponent_Factory() {
return [new ContentQueryComponent(), $r3$.ɵQ(null, SomeDirective, true)];
},
Expand Down Expand Up @@ -772,7 +815,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyApp,
selector: [[['my-app'], null]],
selectors: [['my-app']],
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand Down Expand Up @@ -813,7 +856,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
selector: [[['my-component'], null]],
selectors: [['my-component']],
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand Down Expand Up @@ -881,7 +924,7 @@ describe('compiler compliance', () => {
const LifecycleCompDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: LifecycleComp,
selector: [[['lifecycle-comp'], null]],
selectors: [['lifecycle-comp']],
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
inputs: {nameMin: 'name'},
Expand All @@ -893,7 +936,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleLayout,
selector: [[['simple-layout'], null]],
selectors: [['simple-layout']],
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand Down Expand Up @@ -1001,7 +1044,7 @@ describe('compiler compliance', () => {
const ForDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: ForOfDirective,
selector: [[[null, 'forOf', ''], null]],
selectors: [['', 'forOf', '']],
factory: function ForOfDirective_Factory() {
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
},
Expand All @@ -1015,7 +1058,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
selector: [[['my-component'], null]],
selectors: [['my-component']],
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand Down Expand Up @@ -1093,7 +1136,7 @@ describe('compiler compliance', () => {
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
selector: [[['my-component'], null]],
selectors: [['my-component']],
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
Expand Down