Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class FileLinker<TConstantScope, TStatement, TExpression> {
const minVersion = metaObj.getString('minVersion');
const version = metaObj.getString('version');
const linker = this.linkerSelector.getLinker(declarationFn, minVersion, version);
const definition = linker.linkPartialDeclaration(emitScope.constantPool, metaObj);
const definition = linker.linkPartialDeclaration(emitScope.constantPool, metaObj, version);

return emitScope.translateDefinition(definition);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {BoundTarget, ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, ForwardRefHandling, InterpolationConfig, makeBindingParser, outputAst as o, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareDirectiveDependencyMetadata, R3DeclarePipeDependencyMetadata, R3DeferBlockMetadata, R3DirectiveDependencyMetadata, R3PartialDeclaration, R3TargetBinder, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SelectorMatcher, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstDeferredTrigger, TmplAstElement, ViewEncapsulation} from '@angular/compiler';
import semver from 'semver';

import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
import {Range} from '../../ast/ast_host';
Expand All @@ -15,7 +16,7 @@ import {GetSourceFileFn} from '../get_source_file';

import {toR3DirectiveMeta} from './partial_directive_linker_1';
import {LinkedDefinition, PartialLinker} from './partial_linker';
import {extractForwardRef} from './util';
import {extractForwardRef, PLACEHOLDER_VERSION} from './util';

function makeDirectiveMetadata<TExpression>(
directiveExpr: AstObject<R3DeclareDirectiveDependencyMetadata, TExpression>,
Expand Down Expand Up @@ -49,22 +50,27 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
private code: string) {}

linkPartialDeclaration(
constantPool: ConstantPool,
metaObj: AstObject<R3PartialDeclaration, TExpression>): LinkedDefinition {
const meta = this.toR3ComponentMeta(metaObj);
constantPool: ConstantPool, metaObj: AstObject<R3PartialDeclaration, TExpression>,
version: string): LinkedDefinition {
const meta = this.toR3ComponentMeta(metaObj, version);
return compileComponentFromMetadata(meta, constantPool, makeBindingParser());
}

/**
* This function derives the `R3ComponentMetadata` from the provided AST object.
*/
private toR3ComponentMeta(metaObj: AstObject<R3DeclareComponentMetadata, TExpression>):
R3ComponentMetadata<R3TemplateDependencyMetadata> {
private toR3ComponentMeta(
metaObj: AstObject<R3DeclareComponentMetadata, TExpression>,
version: string): R3ComponentMetadata<R3TemplateDependencyMetadata> {
const interpolation = parseInterpolationConfig(metaObj);
const templateSource = metaObj.getValue('template');
const isInline = metaObj.has('isInline') ? metaObj.getBoolean('isInline') : false;
const templateInfo = this.getTemplateInfo(templateSource, isInline);

// Enable the new block syntax if compiled with v17 and
// above, or when using the local placeholder version.
const supportsBlockSyntax = semver.major(version) >= 17 || version === PLACEHOLDER_VERSION;

const template = parseTemplate(templateInfo.code, templateInfo.sourceUrl, {
escapedString: templateInfo.isEscaped,
interpolationConfig: interpolation,
Expand All @@ -74,6 +80,12 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
metaObj.has('preserveWhitespaces') ? metaObj.getBoolean('preserveWhitespaces') : false,
// We normalize line endings if the template is was inline.
i18nNormalizeLineEndingsInICUs: isInline,

// TODO(crisbeto): hardcode the supported blocks for now. Before the final release
// `enabledBlockTypes` will be replaced with a boolean, at which point `supportsBlockSyntax`
// can be passed in directly here.
enabledBlockTypes: supportsBlockSyntax ? new Set(['if', 'switch', 'for', 'defer']) :
undefined,
});
if (template.errors !== null) {
const errors = template.errors.map(err => err.toString()).join('\n');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ export interface PartialLinker<TExpression> {
* `R3DeclareComponentMetadata` interfaces.
*/
linkPartialDeclaration(
constantPool: ConstantPool,
metaObj: AstObject<R3PartialDeclaration, TExpression>): LinkedDefinition;
constantPool: ConstantPool, metaObj: AstObject<R3PartialDeclaration, TExpression>,
version: string): LinkedDefinition;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {PartialInjectorLinkerVersion1} from './partial_injector_linker_1';
import {PartialLinker} from './partial_linker';
import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1';
import {PartialPipeLinkerVersion1} from './partial_pipe_linker_1';
import {PLACEHOLDER_VERSION} from './util';

export const ɵɵngDeclareDirective = 'ɵɵngDeclareDirective';
export const ɵɵngDeclareClassMetadata = 'ɵɵngDeclareClassMetadata';
Expand Down Expand Up @@ -68,7 +69,7 @@ export function createLinkerMap<TStatement, TExpression>(
environment: LinkerEnvironment<TStatement, TExpression>, sourceUrl: AbsoluteFsPath,
code: string): Map<string, LinkerRange<TExpression>[]> {
const linkers = new Map<string, LinkerRange<TExpression>[]>();
const LATEST_VERSION_RANGE = getRange('<=', '0.0.0-PLACEHOLDER');
const LATEST_VERSION_RANGE = getRange('<=', PLACEHOLDER_VERSION);

linkers.set(ɵɵngDeclareDirective, [
{range: LATEST_VERSION_RANGE, linker: new PartialDirectiveLinkerVersion1(sourceUrl, code)},
Expand Down Expand Up @@ -143,7 +144,7 @@ export class PartialLinkerSelector<TExpression> {
}
const linkerRanges = this.linkers.get(functionName)!;

if (version === '0.0.0-PLACEHOLDER') {
if (version === PLACEHOLDER_VERSION) {
// Special case if the `version` is the same as the current compiler version.
// This helps with compliance tests where the version placeholders have not been replaced.
return linkerRanges[linkerRanges.length - 1].linker;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {createMayBeForwardRefExpression, ForwardRefHandling, MaybeForwardRefExpr
import {AstObject, AstValue} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';

export const PLACEHOLDER_VERSION = '0.0.0-PLACEHOLDER';

export function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference {
return {value: wrapped, type: wrapped};
}
Expand Down
48 changes: 45 additions & 3 deletions packages/compiler-cli/linker/test/file_linker/file_linker_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,48 @@ describe('FileLinker', () => {
});
});

describe('block syntax support', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context for these tests: I couldn't find a better place to test the linker with different version strings.

function linkComponentWithTemplate(version: string, template: string): string {
// Note that the `minVersion` is set to the placeholder,
// because that's what we have in the source code as well.
const source = `
ɵɵngDeclareComponent({
minVersion: "0.0.0-PLACEHOLDER",
version: "${version}",
ngImport: core,
template: \`${template}\`,
isInline: true,
type: SomeComp
});
`;

// We need to create a new source file here, because template parsing requires
// the template string to have offsets which synthetic nodes do not.
const {fileLinker} = createFileLinker(source);
const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true);
const call =
(sourceFile.statements[0] as ts.ExpressionStatement).expression as ts.CallExpression;
const result = fileLinker.linkPartialDeclaration(
'ɵɵngDeclareComponent', [call.arguments[0]], new MockDeclarationScope());
return ts.createPrinter().printNode(ts.EmitHint.Unspecified, result, sourceFile);
}

it('should enable block syntax if compiled with version 17 or above', () => {
for (const version of ['17.0.0', '17.0.1', '17.1.0', '17.0.0-next.0', '18.0.0']) {
expect(linkComponentWithTemplate(version, '@defer {}')).toContain('ɵɵdefer(');
}
});

it('should enable block syntax if compiled with a local version', () => {
expect(linkComponentWithTemplate('0.0.0-PLACEHOLDER', '@defer {}')).toContain('ɵɵdefer(');
});

it('should not enable block syntax if compiled with a version older than 17', () => {
expect(linkComponentWithTemplate('16.2.0', '@Input() is a decorator. This is a brace }'))
.toContain('@Input() is a decorator. This is a brace }');
});
});

describe('getConstantStatements()', () => {
it('should capture shared constant values', () => {
const {fileLinker} = createFileLinker();
Expand Down Expand Up @@ -179,17 +221,17 @@ describe('FileLinker', () => {
});
});

function createFileLinker(): {
function createFileLinker(code = '// test code'): {
host: AstHost<ts.Expression>,
fileLinker: FileLinker<MockConstantScopeRef, ts.Statement, ts.Expression>
fileLinker: FileLinker<MockConstantScopeRef, ts.Statement, ts.Expression>,
} {
const fs = new MockFileSystemNative();
const logger = new MockLogger();
const linkerEnvironment = LinkerEnvironment.create<ts.Statement, ts.Expression>(
fs, logger, new TypeScriptAstHost(),
new TypeScriptAstFactory(/* annotateForClosureCompiler */ false), DEFAULT_LINKER_OPTIONS);
const fileLinker = new FileLinker<MockConstantScopeRef, ts.Statement, ts.Expression>(
linkerEnvironment, fs.resolve('/test.js'), '// test code');
linkerEnvironment, fs.resolve('/test.js'), code);
return {host: linkerEnvironment.host, fileLinker};
}
});
Expand Down
Loading