@angular/core
Version:
Angular - the core framework
1,180 lines (1,166 loc) • 887 kB
JavaScript
'use strict';
/**
* @license Angular v19.2.8
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
'use strict';
var checker = require('./checker-BNmiXJIJ.js');
var ts = require('typescript');
var p = require('path');
require('os');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var p__namespace = /*#__PURE__*/_interopNamespaceDefault(p);
class XmlTagDefinition {
closedByParent = false;
implicitNamespacePrefix = null;
isVoid = false;
ignoreFirstLf = false;
canSelfClose = true;
preventNamespaceInheritance = false;
requireExtraParent(currentParent) {
return false;
}
isClosedByChild(name) {
return false;
}
getContentType() {
return checker.TagContentType.PARSABLE_DATA;
}
}
const _TAG_DEFINITION = new XmlTagDefinition();
function getXmlTagDefinition(tagName) {
return _TAG_DEFINITION;
}
class XmlParser extends checker.Parser {
constructor() {
super(getXmlTagDefinition);
}
parse(source, url, options = {}) {
// Blocks and let declarations aren't supported in an XML context.
return super.parse(source, url, { ...options, tokenizeBlocks: false, tokenizeLet: false });
}
}
const _VERSION$1 = '1.2';
const _XMLNS$1 = 'urn:oasis:names:tc:xliff:document:1.2';
// TODO(vicb): make this a param (s/_/-/)
const _DEFAULT_SOURCE_LANG$1 = 'en';
const _PLACEHOLDER_TAG$1 = 'x';
const _MARKER_TAG$1 = 'mrk';
const _FILE_TAG = 'file';
const _SOURCE_TAG$1 = 'source';
const _SEGMENT_SOURCE_TAG = 'seg-source';
const _ALT_TRANS_TAG = 'alt-trans';
const _TARGET_TAG$1 = 'target';
const _UNIT_TAG$1 = 'trans-unit';
const _CONTEXT_GROUP_TAG = 'context-group';
const _CONTEXT_TAG = 'context';
// https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
// https://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
class Xliff extends checker.Serializer {
write(messages, locale) {
const visitor = new _WriteVisitor$1();
const transUnits = [];
messages.forEach((message) => {
let contextTags = [];
message.sources.forEach((source) => {
let contextGroupTag = new checker.Tag(_CONTEXT_GROUP_TAG, { purpose: 'location' });
contextGroupTag.children.push(new checker.CR(10), new checker.Tag(_CONTEXT_TAG, { 'context-type': 'sourcefile' }, [
new checker.Text$1(source.filePath),
]), new checker.CR(10), new checker.Tag(_CONTEXT_TAG, { 'context-type': 'linenumber' }, [
new checker.Text$1(`${source.startLine}`),
]), new checker.CR(8));
contextTags.push(new checker.CR(8), contextGroupTag);
});
const transUnit = new checker.Tag(_UNIT_TAG$1, { id: message.id, datatype: 'html' });
transUnit.children.push(new checker.CR(8), new checker.Tag(_SOURCE_TAG$1, {}, visitor.serialize(message.nodes)), ...contextTags);
if (message.description) {
transUnit.children.push(new checker.CR(8), new checker.Tag('note', { priority: '1', from: 'description' }, [
new checker.Text$1(message.description),
]));
}
if (message.meaning) {
transUnit.children.push(new checker.CR(8), new checker.Tag('note', { priority: '1', from: 'meaning' }, [new checker.Text$1(message.meaning)]));
}
transUnit.children.push(new checker.CR(6));
transUnits.push(new checker.CR(6), transUnit);
});
const body = new checker.Tag('body', {}, [...transUnits, new checker.CR(4)]);
const file = new checker.Tag('file', {
'source-language': locale || _DEFAULT_SOURCE_LANG$1,
datatype: 'plaintext',
original: 'ng2.template',
}, [new checker.CR(4), body, new checker.CR(2)]);
const xliff = new checker.Tag('xliff', { version: _VERSION$1, xmlns: _XMLNS$1 }, [
new checker.CR(2),
file,
new checker.CR(),
]);
return checker.serialize([
new checker.Declaration({ version: '1.0', encoding: 'UTF-8' }),
new checker.CR(),
xliff,
new checker.CR(),
]);
}
load(content, url) {
// xliff to xml nodes
const xliffParser = new XliffParser();
const { locale, msgIdToHtml, errors } = xliffParser.parse(content, url);
// xml nodes to i18n nodes
const i18nNodesByMsgId = {};
const converter = new XmlToI18n$1();
Object.keys(msgIdToHtml).forEach((msgId) => {
const { i18nNodes, errors: e } = converter.convert(msgIdToHtml[msgId], url);
errors.push(...e);
i18nNodesByMsgId[msgId] = i18nNodes;
});
if (errors.length) {
throw new Error(`xliff parse errors:\n${errors.join('\n')}`);
}
return { locale: locale, i18nNodesByMsgId };
}
digest(message) {
return checker.digest(message);
}
}
let _WriteVisitor$1 = class _WriteVisitor {
visitText(text, context) {
return [new checker.Text$1(text.value)];
}
visitContainer(container, context) {
const nodes = [];
container.children.forEach((node) => nodes.push(...node.visit(this)));
return nodes;
}
visitIcu(icu, context) {
const nodes = [new checker.Text$1(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
Object.keys(icu.cases).forEach((c) => {
nodes.push(new checker.Text$1(`${c} {`), ...icu.cases[c].visit(this), new checker.Text$1(`} `));
});
nodes.push(new checker.Text$1(`}`));
return nodes;
}
visitTagPlaceholder(ph, context) {
const ctype = getCtypeForTag(ph.tag);
if (ph.isVoid) {
// void tags have no children nor closing tags
return [
new checker.Tag(_PLACEHOLDER_TAG$1, { id: ph.startName, ctype, 'equiv-text': `<${ph.tag}/>` }),
];
}
const startTagPh = new checker.Tag(_PLACEHOLDER_TAG$1, {
id: ph.startName,
ctype,
'equiv-text': `<${ph.tag}>`,
});
const closeTagPh = new checker.Tag(_PLACEHOLDER_TAG$1, {
id: ph.closeName,
ctype,
'equiv-text': `</${ph.tag}>`,
});
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
}
visitPlaceholder(ph, context) {
return [new checker.Tag(_PLACEHOLDER_TAG$1, { id: ph.name, 'equiv-text': `{{${ph.value}}}` })];
}
visitBlockPlaceholder(ph, context) {
const ctype = `x-${ph.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
const startTagPh = new checker.Tag(_PLACEHOLDER_TAG$1, {
id: ph.startName,
ctype,
'equiv-text': `@${ph.name}`,
});
const closeTagPh = new checker.Tag(_PLACEHOLDER_TAG$1, { id: ph.closeName, ctype, 'equiv-text': `}` });
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
}
visitIcuPlaceholder(ph, context) {
const equivText = `{${ph.value.expression}, ${ph.value.type}, ${Object.keys(ph.value.cases)
.map((value) => value + ' {...}')
.join(' ')}}`;
return [new checker.Tag(_PLACEHOLDER_TAG$1, { id: ph.name, 'equiv-text': equivText })];
}
serialize(nodes) {
return [].concat(...nodes.map((node) => node.visit(this)));
}
};
// TODO(vicb): add error management (structure)
// Extract messages as xml nodes from the xliff file
class XliffParser {
// using non-null assertions because they're re(set) by parse()
_unitMlString;
_errors;
_msgIdToHtml;
_locale = null;
parse(xliff, url) {
this._unitMlString = null;
this._msgIdToHtml = {};
const xml = new XmlParser().parse(xliff, url);
this._errors = xml.errors;
checker.visitAll(this, xml.rootNodes, null);
return {
msgIdToHtml: this._msgIdToHtml,
errors: this._errors,
locale: this._locale,
};
}
visitElement(element, context) {
switch (element.name) {
case _UNIT_TAG$1:
this._unitMlString = null;
const idAttr = element.attrs.find((attr) => attr.name === 'id');
if (!idAttr) {
this._addError(element, `<${_UNIT_TAG$1}> misses the "id" attribute`);
}
else {
const id = idAttr.value;
if (this._msgIdToHtml.hasOwnProperty(id)) {
this._addError(element, `Duplicated translations for msg ${id}`);
}
else {
checker.visitAll(this, element.children, null);
if (typeof this._unitMlString === 'string') {
this._msgIdToHtml[id] = this._unitMlString;
}
else {
this._addError(element, `Message ${id} misses a translation`);
}
}
}
break;
// ignore those tags
case _SOURCE_TAG$1:
case _SEGMENT_SOURCE_TAG:
case _ALT_TRANS_TAG:
break;
case _TARGET_TAG$1:
const innerTextStart = element.startSourceSpan.end.offset;
const innerTextEnd = element.endSourceSpan.start.offset;
const content = element.startSourceSpan.start.file.content;
const innerText = content.slice(innerTextStart, innerTextEnd);
this._unitMlString = innerText;
break;
case _FILE_TAG:
const localeAttr = element.attrs.find((attr) => attr.name === 'target-language');
if (localeAttr) {
this._locale = localeAttr.value;
}
checker.visitAll(this, element.children, null);
break;
default:
// TODO(vicb): assert file structure, xliff version
// For now only recurse on unhandled nodes
checker.visitAll(this, element.children, null);
}
}
visitAttribute(attribute, context) { }
visitText(text, context) { }
visitComment(comment, context) { }
visitExpansion(expansion, context) { }
visitExpansionCase(expansionCase, context) { }
visitBlock(block, context) { }
visitBlockParameter(parameter, context) { }
visitLetDeclaration(decl, context) { }
_addError(node, message) {
this._errors.push(new checker.I18nError(node.sourceSpan, message));
}
}
// Convert ml nodes (xliff syntax) to i18n nodes
let XmlToI18n$1 = class XmlToI18n {
// using non-null assertion because it's re(set) by convert()
_errors;
convert(message, url) {
const xmlIcu = new XmlParser().parse(message, url, { tokenizeExpansionForms: true });
this._errors = xmlIcu.errors;
const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0
? []
: [].concat(...checker.visitAll(this, xmlIcu.rootNodes));
return {
i18nNodes: i18nNodes,
errors: this._errors,
};
}
visitText(text, context) {
return new checker.Text$2(text.value, text.sourceSpan);
}
visitElement(el, context) {
if (el.name === _PLACEHOLDER_TAG$1) {
const nameAttr = el.attrs.find((attr) => attr.name === 'id');
if (nameAttr) {
return new checker.Placeholder('', nameAttr.value, el.sourceSpan);
}
this._addError(el, `<${_PLACEHOLDER_TAG$1}> misses the "id" attribute`);
return null;
}
if (el.name === _MARKER_TAG$1) {
return [].concat(...checker.visitAll(this, el.children));
}
this._addError(el, `Unexpected tag`);
return null;
}
visitExpansion(icu, context) {
const caseMap = {};
checker.visitAll(this, icu.cases).forEach((c) => {
caseMap[c.value] = new checker.Container(c.nodes, icu.sourceSpan);
});
return new checker.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan);
}
visitExpansionCase(icuCase, context) {
return {
value: icuCase.value,
nodes: checker.visitAll(this, icuCase.expression),
};
}
visitComment(comment, context) { }
visitAttribute(attribute, context) { }
visitBlock(block, context) { }
visitBlockParameter(parameter, context) { }
visitLetDeclaration(decl, context) { }
_addError(node, message) {
this._errors.push(new checker.I18nError(node.sourceSpan, message));
}
};
function getCtypeForTag(tag) {
switch (tag.toLowerCase()) {
case 'br':
return 'lb';
case 'img':
return 'image';
default:
return `x-${tag}`;
}
}
const _VERSION = '2.0';
const _XMLNS = 'urn:oasis:names:tc:xliff:document:2.0';
// TODO(vicb): make this a param (s/_/-/)
const _DEFAULT_SOURCE_LANG = 'en';
const _PLACEHOLDER_TAG = 'ph';
const _PLACEHOLDER_SPANNING_TAG = 'pc';
const _MARKER_TAG = 'mrk';
const _XLIFF_TAG = 'xliff';
const _SOURCE_TAG = 'source';
const _TARGET_TAG = 'target';
const _UNIT_TAG = 'unit';
// https://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html
class Xliff2 extends checker.Serializer {
write(messages, locale) {
const visitor = new _WriteVisitor();
const units = [];
messages.forEach((message) => {
const unit = new checker.Tag(_UNIT_TAG, { id: message.id });
const notes = new checker.Tag('notes');
if (message.description || message.meaning) {
if (message.description) {
notes.children.push(new checker.CR(8), new checker.Tag('note', { category: 'description' }, [new checker.Text$1(message.description)]));
}
if (message.meaning) {
notes.children.push(new checker.CR(8), new checker.Tag('note', { category: 'meaning' }, [new checker.Text$1(message.meaning)]));
}
}
message.sources.forEach((source) => {
notes.children.push(new checker.CR(8), new checker.Tag('note', { category: 'location' }, [
new checker.Text$1(`${source.filePath}:${source.startLine}${source.endLine !== source.startLine ? ',' + source.endLine : ''}`),
]));
});
notes.children.push(new checker.CR(6));
unit.children.push(new checker.CR(6), notes);
const segment = new checker.Tag('segment');
segment.children.push(new checker.CR(8), new checker.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)), new checker.CR(6));
unit.children.push(new checker.CR(6), segment, new checker.CR(4));
units.push(new checker.CR(4), unit);
});
const file = new checker.Tag('file', { 'original': 'ng.template', id: 'ngi18n' }, [
...units,
new checker.CR(2),
]);
const xliff = new checker.Tag(_XLIFF_TAG, { version: _VERSION, xmlns: _XMLNS, srcLang: locale || _DEFAULT_SOURCE_LANG }, [new checker.CR(2), file, new checker.CR()]);
return checker.serialize([
new checker.Declaration({ version: '1.0', encoding: 'UTF-8' }),
new checker.CR(),
xliff,
new checker.CR(),
]);
}
load(content, url) {
// xliff to xml nodes
const xliff2Parser = new Xliff2Parser();
const { locale, msgIdToHtml, errors } = xliff2Parser.parse(content, url);
// xml nodes to i18n nodes
const i18nNodesByMsgId = {};
const converter = new XmlToI18n();
Object.keys(msgIdToHtml).forEach((msgId) => {
const { i18nNodes, errors: e } = converter.convert(msgIdToHtml[msgId], url);
errors.push(...e);
i18nNodesByMsgId[msgId] = i18nNodes;
});
if (errors.length) {
throw new Error(`xliff2 parse errors:\n${errors.join('\n')}`);
}
return { locale: locale, i18nNodesByMsgId };
}
digest(message) {
return checker.decimalDigest(message);
}
}
class _WriteVisitor {
_nextPlaceholderId = 0;
visitText(text, context) {
return [new checker.Text$1(text.value)];
}
visitContainer(container, context) {
const nodes = [];
container.children.forEach((node) => nodes.push(...node.visit(this)));
return nodes;
}
visitIcu(icu, context) {
const nodes = [new checker.Text$1(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
Object.keys(icu.cases).forEach((c) => {
nodes.push(new checker.Text$1(`${c} {`), ...icu.cases[c].visit(this), new checker.Text$1(`} `));
});
nodes.push(new checker.Text$1(`}`));
return nodes;
}
visitTagPlaceholder(ph, context) {
const type = getTypeForTag(ph.tag);
if (ph.isVoid) {
const tagPh = new checker.Tag(_PLACEHOLDER_TAG, {
id: (this._nextPlaceholderId++).toString(),
equiv: ph.startName,
type: type,
disp: `<${ph.tag}/>`,
});
return [tagPh];
}
const tagPc = new checker.Tag(_PLACEHOLDER_SPANNING_TAG, {
id: (this._nextPlaceholderId++).toString(),
equivStart: ph.startName,
equivEnd: ph.closeName,
type: type,
dispStart: `<${ph.tag}>`,
dispEnd: `</${ph.tag}>`,
});
const nodes = [].concat(...ph.children.map((node) => node.visit(this)));
if (nodes.length) {
nodes.forEach((node) => tagPc.children.push(node));
}
else {
tagPc.children.push(new checker.Text$1(''));
}
return [tagPc];
}
visitPlaceholder(ph, context) {
const idStr = (this._nextPlaceholderId++).toString();
return [
new checker.Tag(_PLACEHOLDER_TAG, {
id: idStr,
equiv: ph.name,
disp: `{{${ph.value}}}`,
}),
];
}
visitBlockPlaceholder(ph, context) {
const tagPc = new checker.Tag(_PLACEHOLDER_SPANNING_TAG, {
id: (this._nextPlaceholderId++).toString(),
equivStart: ph.startName,
equivEnd: ph.closeName,
type: 'other',
dispStart: `@${ph.name}`,
dispEnd: `}`,
});
const nodes = [].concat(...ph.children.map((node) => node.visit(this)));
if (nodes.length) {
nodes.forEach((node) => tagPc.children.push(node));
}
else {
tagPc.children.push(new checker.Text$1(''));
}
return [tagPc];
}
visitIcuPlaceholder(ph, context) {
const cases = Object.keys(ph.value.cases)
.map((value) => value + ' {...}')
.join(' ');
const idStr = (this._nextPlaceholderId++).toString();
return [
new checker.Tag(_PLACEHOLDER_TAG, {
id: idStr,
equiv: ph.name,
disp: `{${ph.value.expression}, ${ph.value.type}, ${cases}}`,
}),
];
}
serialize(nodes) {
this._nextPlaceholderId = 0;
return [].concat(...nodes.map((node) => node.visit(this)));
}
}
// Extract messages as xml nodes from the xliff file
class Xliff2Parser {
// using non-null assertions because they're all (re)set by parse()
_unitMlString;
_errors;
_msgIdToHtml;
_locale = null;
parse(xliff, url) {
this._unitMlString = null;
this._msgIdToHtml = {};
const xml = new XmlParser().parse(xliff, url);
this._errors = xml.errors;
checker.visitAll(this, xml.rootNodes, null);
return {
msgIdToHtml: this._msgIdToHtml,
errors: this._errors,
locale: this._locale,
};
}
visitElement(element, context) {
switch (element.name) {
case _UNIT_TAG:
this._unitMlString = null;
const idAttr = element.attrs.find((attr) => attr.name === 'id');
if (!idAttr) {
this._addError(element, `<${_UNIT_TAG}> misses the "id" attribute`);
}
else {
const id = idAttr.value;
if (this._msgIdToHtml.hasOwnProperty(id)) {
this._addError(element, `Duplicated translations for msg ${id}`);
}
else {
checker.visitAll(this, element.children, null);
if (typeof this._unitMlString === 'string') {
this._msgIdToHtml[id] = this._unitMlString;
}
else {
this._addError(element, `Message ${id} misses a translation`);
}
}
}
break;
case _SOURCE_TAG:
// ignore source message
break;
case _TARGET_TAG:
const innerTextStart = element.startSourceSpan.end.offset;
const innerTextEnd = element.endSourceSpan.start.offset;
const content = element.startSourceSpan.start.file.content;
const innerText = content.slice(innerTextStart, innerTextEnd);
this._unitMlString = innerText;
break;
case _XLIFF_TAG:
const localeAttr = element.attrs.find((attr) => attr.name === 'trgLang');
if (localeAttr) {
this._locale = localeAttr.value;
}
const versionAttr = element.attrs.find((attr) => attr.name === 'version');
if (versionAttr) {
const version = versionAttr.value;
if (version !== '2.0') {
this._addError(element, `The XLIFF file version ${version} is not compatible with XLIFF 2.0 serializer`);
}
else {
checker.visitAll(this, element.children, null);
}
}
break;
default:
checker.visitAll(this, element.children, null);
}
}
visitAttribute(attribute, context) { }
visitText(text, context) { }
visitComment(comment, context) { }
visitExpansion(expansion, context) { }
visitExpansionCase(expansionCase, context) { }
visitBlock(block, context) { }
visitBlockParameter(parameter, context) { }
visitLetDeclaration(decl, context) { }
_addError(node, message) {
this._errors.push(new checker.I18nError(node.sourceSpan, message));
}
}
// Convert ml nodes (xliff syntax) to i18n nodes
class XmlToI18n {
// using non-null assertion because re(set) by convert()
_errors;
convert(message, url) {
const xmlIcu = new XmlParser().parse(message, url, { tokenizeExpansionForms: true });
this._errors = xmlIcu.errors;
const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0
? []
: [].concat(...checker.visitAll(this, xmlIcu.rootNodes));
return {
i18nNodes,
errors: this._errors,
};
}
visitText(text, context) {
return new checker.Text$2(text.value, text.sourceSpan);
}
visitElement(el, context) {
switch (el.name) {
case _PLACEHOLDER_TAG:
const nameAttr = el.attrs.find((attr) => attr.name === 'equiv');
if (nameAttr) {
return [new checker.Placeholder('', nameAttr.value, el.sourceSpan)];
}
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equiv" attribute`);
break;
case _PLACEHOLDER_SPANNING_TAG:
const startAttr = el.attrs.find((attr) => attr.name === 'equivStart');
const endAttr = el.attrs.find((attr) => attr.name === 'equivEnd');
if (!startAttr) {
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equivStart" attribute`);
}
else if (!endAttr) {
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equivEnd" attribute`);
}
else {
const startId = startAttr.value;
const endId = endAttr.value;
const nodes = [];
return nodes.concat(new checker.Placeholder('', startId, el.sourceSpan), ...el.children.map((node) => node.visit(this, null)), new checker.Placeholder('', endId, el.sourceSpan));
}
break;
case _MARKER_TAG:
return [].concat(...checker.visitAll(this, el.children));
default:
this._addError(el, `Unexpected tag`);
}
return null;
}
visitExpansion(icu, context) {
const caseMap = {};
checker.visitAll(this, icu.cases).forEach((c) => {
caseMap[c.value] = new checker.Container(c.nodes, icu.sourceSpan);
});
return new checker.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan);
}
visitExpansionCase(icuCase, context) {
return {
value: icuCase.value,
nodes: [].concat(...checker.visitAll(this, icuCase.expression)),
};
}
visitComment(comment, context) { }
visitAttribute(attribute, context) { }
visitBlock(block, context) { }
visitBlockParameter(parameter, context) { }
visitLetDeclaration(decl, context) { }
_addError(node, message) {
this._errors.push(new checker.I18nError(node.sourceSpan, message));
}
}
function getTypeForTag(tag) {
switch (tag.toLowerCase()) {
case 'br':
case 'b':
case 'i':
case 'u':
return 'fmt';
case 'img':
return 'image';
case 'a':
return 'link';
default:
return 'other';
}
}
/**
* A container for message extracted from the templates.
*/
class MessageBundle {
_htmlParser;
_implicitTags;
_implicitAttrs;
_locale;
_preserveWhitespace;
_messages = [];
constructor(_htmlParser, _implicitTags, _implicitAttrs, _locale = null, _preserveWhitespace = true) {
this._htmlParser = _htmlParser;
this._implicitTags = _implicitTags;
this._implicitAttrs = _implicitAttrs;
this._locale = _locale;
this._preserveWhitespace = _preserveWhitespace;
}
updateFromTemplate(source, url, interpolationConfig) {
const htmlParserResult = this._htmlParser.parse(source, url, {
tokenizeExpansionForms: true,
interpolationConfig,
});
if (htmlParserResult.errors.length) {
return htmlParserResult.errors;
}
// Trim unnecessary whitespace from extracted messages if requested. This
// makes the messages more durable to trivial whitespace changes without
// affected message IDs.
const rootNodes = this._preserveWhitespace
? htmlParserResult.rootNodes
: checker.visitAllWithSiblings(new checker.WhitespaceVisitor(/* preserveSignificantWhitespace */ false), htmlParserResult.rootNodes);
const i18nParserResult = checker.extractMessages(rootNodes, interpolationConfig, this._implicitTags, this._implicitAttrs,
/* preserveSignificantWhitespace */ this._preserveWhitespace);
if (i18nParserResult.errors.length) {
return i18nParserResult.errors;
}
this._messages.push(...i18nParserResult.messages);
return [];
}
// Return the message in the internal format
// The public (serialized) format might be different, see the `write` method.
getMessages() {
return this._messages;
}
write(serializer, filterSources) {
const messages = {};
const mapperVisitor = new MapPlaceholderNames();
// Deduplicate messages based on their ID
this._messages.forEach((message) => {
const id = serializer.digest(message);
if (!messages.hasOwnProperty(id)) {
messages[id] = message;
}
else {
messages[id].sources.push(...message.sources);
}
});
// Transform placeholder names using the serializer mapping
const msgList = Object.keys(messages).map((id) => {
const mapper = serializer.createNameMapper(messages[id]);
const src = messages[id];
const nodes = mapper ? mapperVisitor.convert(src.nodes, mapper) : src.nodes;
let transformedMessage = new checker.Message(nodes, {}, {}, src.meaning, src.description, id);
transformedMessage.sources = src.sources;
if (filterSources) {
transformedMessage.sources.forEach((source) => (source.filePath = filterSources(source.filePath)));
}
return transformedMessage;
});
return serializer.write(msgList, this._locale);
}
}
// Transform an i18n AST by renaming the placeholder nodes with the given mapper
class MapPlaceholderNames extends checker.CloneVisitor {
convert(nodes, mapper) {
return mapper ? nodes.map((n) => n.visit(this, mapper)) : nodes;
}
visitTagPlaceholder(ph, mapper) {
const startName = mapper.toPublicName(ph.startName);
const closeName = ph.closeName ? mapper.toPublicName(ph.closeName) : ph.closeName;
const children = ph.children.map((n) => n.visit(this, mapper));
return new checker.TagPlaceholder(ph.tag, ph.attrs, startName, closeName, children, ph.isVoid, ph.sourceSpan, ph.startSourceSpan, ph.endSourceSpan);
}
visitBlockPlaceholder(ph, mapper) {
const startName = mapper.toPublicName(ph.startName);
const closeName = ph.closeName ? mapper.toPublicName(ph.closeName) : ph.closeName;
const children = ph.children.map((n) => n.visit(this, mapper));
return new checker.BlockPlaceholder(ph.name, ph.parameters, startName, closeName, children, ph.sourceSpan, ph.startSourceSpan, ph.endSourceSpan);
}
visitPlaceholder(ph, mapper) {
return new checker.Placeholder(ph.value, mapper.toPublicName(ph.name), ph.sourceSpan);
}
visitIcuPlaceholder(ph, mapper) {
return new checker.IcuPlaceholder(ph.value, mapper.toPublicName(ph.name), ph.sourceSpan);
}
}
function compileClassMetadata(metadata) {
const fnCall = internalCompileClassMetadata(metadata);
return checker.arrowFn([], [checker.devOnlyGuardedExpression(fnCall).toStmt()]).callFn([]);
}
/** Compiles only the `setClassMetadata` call without any additional wrappers. */
function internalCompileClassMetadata(metadata) {
return checker.importExpr(checker.Identifiers.setClassMetadata)
.callFn([
metadata.type,
metadata.decorators,
metadata.ctorParameters ?? checker.literal(null),
metadata.propDecorators ?? checker.literal(null),
]);
}
/**
* Wraps the `setClassMetadata` function with extra logic that dynamically
* loads dependencies from `@defer` blocks.
*
* Generates a call like this:
* ```ts
* setClassMetadataAsync(type, () => [
* import('./cmp-a').then(m => m.CmpA);
* import('./cmp-b').then(m => m.CmpB);
* ], (CmpA, CmpB) => {
* setClassMetadata(type, decorators, ctorParameters, propParameters);
* });
* ```
*
* Similar to the `setClassMetadata` call, it's wrapped into the `ngDevMode`
* check to tree-shake away this code in production mode.
*/
function compileComponentClassMetadata(metadata, dependencies) {
if (dependencies === null || dependencies.length === 0) {
// If there are no deferrable symbols - just generate a regular `setClassMetadata` call.
return compileClassMetadata(metadata);
}
return internalCompileSetClassMetadataAsync(metadata, dependencies.map((dep) => new checker.FnParam(dep.symbolName, checker.DYNAMIC_TYPE)), compileComponentMetadataAsyncResolver(dependencies));
}
/**
* Internal logic used to compile a `setClassMetadataAsync` call.
* @param metadata Class metadata for the internal `setClassMetadata` call.
* @param wrapperParams Parameters to be set on the callback that wraps `setClassMetata`.
* @param dependencyResolverFn Function to resolve the deferred dependencies.
*/
function internalCompileSetClassMetadataAsync(metadata, wrapperParams, dependencyResolverFn) {
// Omit the wrapper since it'll be added around `setClassMetadataAsync` instead.
const setClassMetadataCall = internalCompileClassMetadata(metadata);
const setClassMetaWrapper = checker.arrowFn(wrapperParams, [setClassMetadataCall.toStmt()]);
const setClassMetaAsync = checker.importExpr(checker.Identifiers.setClassMetadataAsync)
.callFn([metadata.type, dependencyResolverFn, setClassMetaWrapper]);
return checker.arrowFn([], [checker.devOnlyGuardedExpression(setClassMetaAsync).toStmt()]).callFn([]);
}
/**
* Compiles the function that loads the dependencies for the
* entire component in `setClassMetadataAsync`.
*/
function compileComponentMetadataAsyncResolver(dependencies) {
const dynamicImports = dependencies.map(({ symbolName, importPath, isDefaultImport }) => {
// e.g. `(m) => m.CmpA`
const innerFn =
// Default imports are always accessed through the `default` property.
checker.arrowFn([new checker.FnParam('m', checker.DYNAMIC_TYPE)], checker.variable('m').prop(isDefaultImport ? 'default' : symbolName));
// e.g. `import('./cmp-a').then(...)`
return new checker.DynamicImportExpr(importPath).prop('then').callFn([innerFn]);
});
// e.g. `() => [ ... ];`
return checker.arrowFn([], checker.literalArr(dynamicImports));
}
/**
* Generate an ngDevMode guarded call to setClassDebugInfo with the debug info about the class
* (e.g., the file name in which the class is defined)
*/
function compileClassDebugInfo(debugInfo) {
const debugInfoObject = {
className: debugInfo.className,
};
// Include file path and line number only if the file relative path is calculated successfully.
if (debugInfo.filePath) {
debugInfoObject.filePath = debugInfo.filePath;
debugInfoObject.lineNumber = debugInfo.lineNumber;
}
// Include forbidOrphanRendering only if it's set to true (to reduce generated code)
if (debugInfo.forbidOrphanRendering) {
debugInfoObject.forbidOrphanRendering = checker.literal(true);
}
const fnCall = checker.importExpr(checker.Identifiers.setClassDebugInfo)
.callFn([debugInfo.type, checker.mapLiteral(debugInfoObject)]);
const iife = checker.arrowFn([], [checker.devOnlyGuardedExpression(fnCall).toStmt()]);
return iife.callFn([]);
}
/*!
* @license
* Copyright Google LLC 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.dev/license
*/
/**
* Compiles the expression that initializes HMR for a class.
* @param meta HMR metadata extracted from the class.
*/
function compileHmrInitializer(meta) {
const moduleName = 'm';
const dataName = 'd';
const timestampName = 't';
const idName = 'id';
const importCallbackName = `${meta.className}_HmrLoad`;
const namespaces = meta.namespaceDependencies.map((dep) => {
return new checker.ExternalExpr({ moduleName: dep.moduleName, name: null });
});
// m.default
const defaultRead = checker.variable(moduleName).prop('default');
// ɵɵreplaceMetadata(Comp, m.default, [...namespaces], [...locals], import.meta, id);
const replaceCall = checker.importExpr(checker.Identifiers.replaceMetadata)
.callFn([
meta.type,
defaultRead,
checker.literalArr(namespaces),
checker.literalArr(meta.localDependencies.map((l) => l.runtimeRepresentation)),
checker.variable('import').prop('meta'),
checker.variable(idName),
]);
// (m) => m.default && ɵɵreplaceMetadata(...)
const replaceCallback = checker.arrowFn([new checker.FnParam(moduleName)], defaultRead.and(replaceCall));
// '<url>?c=' + id + '&t=' + encodeURIComponent(t)
const urlValue = checker.literal(`./@ng/component?c=`)
.plus(checker.variable(idName))
.plus(checker.literal('&t='))
.plus(checker.variable('encodeURIComponent').callFn([checker.variable(timestampName)]));
// import.meta.url
const urlBase = checker.variable('import').prop('meta').prop('url');
// new URL(urlValue, urlBase).href
const urlHref = new checker.InstantiateExpr(checker.variable('URL'), [urlValue, urlBase]).prop('href');
// function Cmp_HmrLoad(t) {
// import(/* @vite-ignore */ urlHref).then((m) => m.default && replaceMetadata(...));
// }
const importCallback = new checker.DeclareFunctionStmt(importCallbackName, [new checker.FnParam(timestampName)], [
// The vite-ignore special comment is required to prevent Vite from generating a superfluous
// warning for each usage within the development code. If Vite provides a method to
// programmatically avoid this warning in the future, this added comment can be removed here.
new checker.DynamicImportExpr(urlHref, null, '@vite-ignore')
.prop('then')
.callFn([replaceCallback])
.toStmt(),
], null, checker.StmtModifier.Final);
// (d) => d.id === id && Cmp_HmrLoad(d.timestamp)
const updateCallback = checker.arrowFn([new checker.FnParam(dataName)], checker.variable(dataName)
.prop('id')
.identical(checker.variable(idName))
.and(checker.variable(importCallbackName).callFn([checker.variable(dataName).prop('timestamp')])));
// Cmp_HmrLoad(Date.now());
// Initial call to kick off the loading in order to avoid edge cases with components
// coming from lazy chunks that change before the chunk has loaded.
const initialCall = checker.variable(importCallbackName)
.callFn([checker.variable('Date').prop('now').callFn([])]);
// import.meta.hot
const hotRead = checker.variable('import').prop('meta').prop('hot');
// import.meta.hot.on('angular:component-update', () => ...);
const hotListener = hotRead
.clone()
.prop('on')
.callFn([checker.literal('angular:component-update'), updateCallback]);
return checker.arrowFn([], [
// const id = <id>;
new checker.DeclareVarStmt(idName, checker.literal(encodeURIComponent(`${meta.filePath}@${meta.className}`)), null, checker.StmtModifier.Final),
// function Cmp_HmrLoad() {...}.
importCallback,
// ngDevMode && Cmp_HmrLoad(Date.now());
checker.devOnlyGuardedExpression(initialCall).toStmt(),
// ngDevMode && import.meta.hot && import.meta.hot.on(...)
checker.devOnlyGuardedExpression(hotRead.and(hotListener)).toStmt(),
])
.callFn([]);
}
/**
* Compiles the HMR update callback for a class.
* @param definitions Compiled definitions for the class (e.g. `defineComponent` calls).
* @param constantStatements Supporting constants statements that were generated alongside
* the definition.
* @param meta HMR metadata extracted from the class.
*/
function compileHmrUpdateCallback(definitions, constantStatements, meta) {
const namespaces = 'ɵɵnamespaces';
const params = [meta.className, namespaces].map((name) => new checker.FnParam(name, checker.DYNAMIC_TYPE));
const body = [];
for (const local of meta.localDependencies) {
params.push(new checker.FnParam(local.name));
}
// Declare variables that read out the individual namespaces.
for (let i = 0; i < meta.namespaceDependencies.length; i++) {
body.push(new checker.DeclareVarStmt(meta.namespaceDependencies[i].assignedName, checker.variable(namespaces).key(checker.literal(i)), checker.DYNAMIC_TYPE, checker.StmtModifier.Final));
}
body.push(...constantStatements);
for (const field of definitions) {
if (field.initializer !== null) {
body.push(checker.variable(meta.className).prop(field.name).set(field.initializer).toStmt());
for (const stmt of field.statements) {
body.push(stmt);
}
}
}
return new checker.DeclareFunctionStmt(`${meta.className}_UpdateMetadata`, params, body, null, checker.StmtModifier.Final);
}
/**
* Every time we make a breaking change to the declaration interface or partial-linker behavior, we
* must update this constant to prevent old partial-linkers from incorrectly processing the
* declaration.
*
* Do not include any prerelease in these versions as they are ignored.
*/
const MINIMUM_PARTIAL_LINKER_VERSION$5 = '12.0.0';
/**
* Minimum version at which deferred blocks are supported in the linker.
*/
const MINIMUM_PARTIAL_LINKER_DEFER_SUPPORT_VERSION = '18.0.0';
function compileDeclareClassMetadata(metadata) {
const definitionMap = new checker.DefinitionMap();
definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_VERSION$5));
definitionMap.set('version', checker.literal('19.2.8'));
definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
definitionMap.set('type', metadata.type);
definitionMap.set('decorators', metadata.decorators);
definitionMap.set('ctorParameters', metadata.ctorParameters);
definitionMap.set('propDecorators', metadata.propDecorators);
return checker.importExpr(checker.Identifiers.declareClassMetadata).callFn([definitionMap.toLiteralMap()]);
}
function compileComponentDeclareClassMetadata(metadata, dependencies) {
if (dependencies === null || dependencies.length === 0) {
return compileDeclareClassMetadata(metadata);
}
const definitionMap = new checker.DefinitionMap();
const callbackReturnDefinitionMap = new checker.DefinitionMap();
callbackReturnDefinitionMap.set('decorators', metadata.decorators);
callbackReturnDefinitionMap.set('ctorParameters', metadata.ctorParameters ?? checker.literal(null));
callbackReturnDefinitionMap.set('propDecorators', metadata.propDecorators ?? checker.literal(null));
definitionMap.set('minVersion', checker.literal(MINIMUM_PARTIAL_LINKER_DEFER_SUPPORT_VERSION));
definitionMap.set('version', checker.literal('19.2.8'));
definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
definitionMap.set('type', metadata.type);
definitionMap.set('resolveDeferredDeps', compileComponentMetadataAsyncResolver(dependencies));
definitionMap.set('resolveMetadata', checker.arrowFn(dependencies.map((dep) => new checker.FnParam(dep.symbolName, checker.DYNAMIC_TYPE)), callbackReturnDefinitionMap.toLiteralMap()));
return checker.importExpr(checker.Identifiers.declareClassMetadataAsync).callFn([definitionMap.toLiteralMap()]);
}
/**
* Creates an array literal expression from the given array, mapping all values to an expression
* using the provided mapping function. If the array is empty or null, then null is returned.
*
* @param values The array to transfer into literal array expression.
* @param mapper The logic to use for creating an expression for the array's values.
* @returns An array literal expression representing `values`, or null if `values` is empty or
* is itself null.
*/
function toOptionalLiteralArray(values, mapper) {
if (values === null || values.length === 0) {
return null;
}
return checker.literalArr(values.map((value) => mapper(value)));
}
/**
* Creates an object literal expression from the given object, mapping all values to an expression
* using the provided mapping function. If the object has no keys, then null is returned.
*
* @param object The object to transfer into an object literal expression.
* @param mapper The logic to use for creating an expression for the object's values.
* @returns An object literal expression representing `object`, or null if `object` does not have
* any keys.
*/
function toOptionalLiteralMap(object, mapper) {
const entries = Object.keys(object).map((key) => {
const value = object[key];
return { key, value: mapper(value), quoted: true };
});
if (entries.length > 0) {
return checker.literalMap(entries);
}
else {
return null;
}
}
function compileDependencies(deps) {
if (deps === 'invalid') {
// The `deps` can be set to the string "invalid" by the `unwrapConstructorDependencies()`
// function, which tries to convert `ConstructorDeps` into `R3DependencyMetadata[]`.
return checker.literal('invalid');
}
else if (deps === null) {
return checker.literal(null);
}
else {
return checker.literalArr(deps.map(compileDependency));
}
}
function compileDependency(dep) {
const depMeta = new checker.DefinitionMap();
depMeta.set('token', dep.token);
if (dep.attributeNameType !== null) {
depMeta.set('attribute', checker.literal(true));
}
if (dep.host) {
depMeta.set('host', checker.literal(true));
}
if (dep.optional) {
depMeta.set('optional', checker.literal(true));
}
if (dep.self) {
depMeta.set('self', checker.literal(true));
}
if (dep.skipSelf) {
depMeta.set('skipSelf', checker.literal(true));
}
return depMeta.toLiteralMap();
}
/**
* Compile a directive declaration defined by the `R3DirectiveMetadata`.
*/
function compileDeclareDirectiveFromMetadata(meta) {
const definitionMap = createDirectiveDefinitionMap(meta);
const expression = checker.importExpr(checker.Identifiers.declareDirective).callFn([definitionMap.toLiteralMap()]);
const type = checker.createDirectiveType(meta);
return { expression, type, statements: [] };
}
/**
* Gathers the declaration fields for a directive into a `DefinitionMap`. This allows for reusing
* this logic for components, as they extend the directive metadata.
*/
function createDirectiveDefinitionMap(meta) {
const definitionMap = new checker.DefinitionMap();
const minVersion = getMinimumVersionForPartialOutput(meta);
definitionMap.set('minVersion', checker.literal(minVersion));
definitionMap.set('version', checker.literal('19.2.8'));
// e.g. `type: MyDirective`
definitionMap.set('type', meta.type.value);
if (meta.isStandalone !== undefined) {
definitionMap.set('isStandalone', checker.literal(meta.isStandalone));
}
if (meta.isSignal) {
definitionMap.set('isSignal', checker.literal(meta.isSignal));
}
// e.g. `selector: 'some-dir'`
if (meta.selector !== null) {
definitionMap.set('selector', checker.literal(meta.selector));
}
definitionMap.set('inputs', needsNewInputPartialOutput(meta)
? createInputsPartialMetadata(meta.inputs)
: legacyInputsPartialMetadata(meta.inputs));
definitionMap.set('outputs', checker.conditionallyCreateDirectiveBindingLiteral(meta.outputs));
definitionMap.set('host', compileHostMetadata(meta.host));
definitionMap.set('providers', meta.providers);
if (meta.queries.length > 0) {
definitionMap.set('queries', checker.literalArr(meta.queries.map(compileQuery)));
}
if (meta.viewQueries.length > 0) {
definitionMap.set('viewQueries', checker.literalArr(meta.viewQueries.map(compileQuery)));
}
if (meta.exportAs !== null) {
definitionMap.set('exportAs', checker.asLiteral(meta.exportAs));
}
if (meta.usesInheritance) {
definitionMap.set('usesInheritance', checker.literal(true));
}
if (meta.lifecycle.usesOnChanges) {
definitionMap.set('usesOnChanges', checker.literal(true));
}
if (meta.hostDirectives?.length) {
definitionMap.set('hostDirectives', createHostDirectives(meta.hostDirectives));
}
definitionMap.set('ngImport', checker.importExpr(checker.Identifiers.core));
return definitionMap;
}
/**
* Determines the minimum linker version for the partial output
* generated for this directive.
*
* Every time we make a breaking change to the declaration interface or partial-linker
* behavior, we must update the minimum versions to prevent old partial-linkers from
* incorrectly processing the declaration.
*
* NOTE: Do not include any prerelease in these versions as they are ignored.
*/
function getMinimumVersionForPartialOutput(meta) {
// We are starting with the oldest minimum version that can work for common
// directive partial compilation output. As we discover usages of new features
// that require a newer partial output emit, we bump the `minVersion`. Our goal
// is to keep libraries as much compatible with older linker versions as possible.
let minVersion = '14.0.0';
// Note: in order to allow consuming Angular libraries that have been compiled with 16.1+ in
// Angular 16.0, we only force a minimum version of 16.1 if input transform feature as