UNPKG

@angular/core

Version:

Angular - the core framework

1,180 lines (1,166 loc) 886 kB
'use strict'; /** * @license Angular v19.2.7 * (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.7')); 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.7')); 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.7')); // 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