@angular/language-service
Version:
Angular - language services
1,263 lines (1,247 loc) • 14.2 MB
JavaScript
/**
* @license Angular v22.0.4
* Copyright Google LLC All Rights Reserved.
* License: MIT
*/
let $deferred;
function define(modules, callback) {
$deferred = {modules, callback};
}
module.exports = function(provided) {
const ts = provided['typescript'];
if (!ts) {
throw new Error('Caller does not provide typescript module');
}
const results = {};
const resolvedModules = $deferred.modules.map(m => {
if (m === 'exports') {
return results;
}
if (m === 'typescript') {
return ts;
}
return require(m);
});
$deferred.callback(...resolvedModules);
return results;
};
define(['module', 'exports', 'os', 'typescript', 'fs', 'module', 'path', 'url', 'node:path', 'assert'], (function (module, exports, os, ts, fs$1, module$1, p, url, path, assert) { 'use strict';
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 os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
var p__namespace = /*#__PURE__*/_interopNamespaceDefault(p);
var url__namespace = /*#__PURE__*/_interopNamespaceDefault(url);
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
/**
* @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
*/
/**
* Angular-specific LSP SymbolKind values for template symbols.
* These are used for symbols that don't have a direct TypeScript ScriptElementKind mapping.
* Values match LSP SymbolKind enum: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#symbolKind
*/
var AngularSymbolKind;
(function (AngularSymbolKind) {
AngularSymbolKind[AngularSymbolKind["Namespace"] = 3] = "Namespace";
AngularSymbolKind[AngularSymbolKind["Class"] = 5] = "Class";
AngularSymbolKind[AngularSymbolKind["Array"] = 18] = "Array";
AngularSymbolKind[AngularSymbolKind["Object"] = 19] = "Object";
AngularSymbolKind[AngularSymbolKind["Struct"] = 23] = "Struct";
AngularSymbolKind[AngularSymbolKind["Event"] = 24] = "Event";
})(AngularSymbolKind || (AngularSymbolKind = {}));
function isNgLanguageService(ls) {
return 'getTcb' in ls;
}
/**
* The default `FileSystem` that will always fail.
*
* This is a way of ensuring that the developer consciously chooses and
* configures the `FileSystem` before using it; particularly important when
* considering static functions like `absoluteFrom()` which rely on
* the `FileSystem` under the hood.
*/
class InvalidFileSystem {
exists(path) {
throw makeError();
}
readFile(path) {
throw makeError();
}
readFileBuffer(path) {
throw makeError();
}
writeFile(path, data, exclusive) {
throw makeError();
}
removeFile(path) {
throw makeError();
}
symlink(target, path) {
throw makeError();
}
readdir(path) {
throw makeError();
}
lstat(path) {
throw makeError();
}
stat(path) {
throw makeError();
}
pwd() {
throw makeError();
}
chdir(path) {
throw makeError();
}
extname(path) {
throw makeError();
}
copyFile(from, to) {
throw makeError();
}
moveFile(from, to) {
throw makeError();
}
ensureDir(path) {
throw makeError();
}
removeDeep(path) {
throw makeError();
}
isCaseSensitive() {
throw makeError();
}
resolve(...paths) {
throw makeError();
}
dirname(file) {
throw makeError();
}
join(basePath, ...paths) {
throw makeError();
}
isRoot(path) {
throw makeError();
}
isRooted(path) {
throw makeError();
}
relative(from, to) {
throw makeError();
}
basename(filePath, extension) {
throw makeError();
}
realpath(filePath) {
throw makeError();
}
getDefaultLibLocation() {
throw makeError();
}
normalize(path) {
throw makeError();
}
}
function makeError() {
return new Error('FileSystem has not been configured. Please call `setFileSystem()` before calling this method.');
}
const TS_DTS_TSX_JS_EXTENSION = /(?:\.d)?\.ts$|\.tsx$|\.js$/;
/**
* Remove a .ts, .d.ts, .tsx, or .js extension from a file name.
*/
function stripExtension(path) {
return path.replace(TS_DTS_TSX_JS_EXTENSION, '');
}
function getSourceFileOrError(program, fileName) {
const sf = program.getSourceFile(fileName);
if (sf === undefined) {
throw new Error(`Program does not contain "${fileName}" - available files are ${program
.getSourceFiles()
.map((sf) => sf.fileName)
.join(', ')}`);
}
return sf;
}
let fs = new InvalidFileSystem();
function getFileSystem() {
return fs;
}
function setFileSystem(fileSystem) {
fs = fileSystem;
}
/**
* Convert the path `path` to an `AbsoluteFsPath`, throwing an error if it's not an absolute path.
*/
function absoluteFrom(path) {
if (!fs.isRooted(path)) {
throw new Error(`Internal Error: absoluteFrom(${path}): path is not absolute`);
}
return fs.resolve(path);
}
const ABSOLUTE_PATH = Symbol('AbsolutePath');
/**
* Extract an `AbsoluteFsPath` from a `ts.SourceFile`-like object.
*/
function absoluteFromSourceFile(sf) {
const sfWithPatch = sf;
if (sfWithPatch[ABSOLUTE_PATH] === undefined) {
sfWithPatch[ABSOLUTE_PATH] = fs.resolve(sfWithPatch.fileName);
}
// Non-null assertion needed since TS doesn't narrow the type of fields that use a symbol as a key
// apparently.
return sfWithPatch[ABSOLUTE_PATH];
}
/**
* Static access to `dirname`.
*/
function dirname(file) {
return fs.dirname(file);
}
/**
* Static access to `join`.
*/
function join(basePath, ...paths) {
return fs.join(basePath, ...paths);
}
/**
* Static access to `resolve`s.
*/
function resolve(basePath, ...paths) {
return fs.resolve(basePath, ...paths);
}
/**
* Static access to `isRooted`.
*/
function isRooted(path) {
return fs.isRooted(path);
}
/**
* Static access to `relative`.
*/
function relative(from, to) {
return fs.relative(from, to);
}
/**
* Returns true if the given path is locally relative.
*
* This is used to work out if the given path is relative (i.e. not absolute) but also is not
* escaping the current directory.
*/
function isLocalRelativePath(relativePath) {
return !isRooted(relativePath) && !relativePath.startsWith('..');
}
/**
* Converts a path to a form suitable for use as a relative module import specifier.
*
* In other words it adds the `./` to the path if it is locally relative.
*/
function toRelativeImport(relativePath) {
return isLocalRelativePath(relativePath) ? `./${relativePath}` : relativePath;
}
/**
* @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
*/
/// <reference types="node" />
class NgtscCompilerHost {
fs;
options;
constructor(fs, options = {}) {
this.fs = fs;
this.options = options;
}
getSourceFile(fileName, languageVersion) {
const text = this.readFile(fileName);
return text !== undefined
? ts.createSourceFile(fileName, text, languageVersion, true)
: undefined;
}
getDefaultLibFileName(options) {
return this.fs.join(this.getDefaultLibLocation(), ts.getDefaultLibFileName(options));
}
getDefaultLibLocation() {
return this.fs.getDefaultLibLocation();
}
writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles) {
const path = absoluteFrom(fileName);
this.fs.ensureDir(this.fs.dirname(path));
this.fs.writeFile(path, data);
}
getCurrentDirectory() {
return this.fs.pwd();
}
getCanonicalFileName(fileName) {
return this.useCaseSensitiveFileNames() ? fileName : fileName.toLowerCase();
}
useCaseSensitiveFileNames() {
return this.fs.isCaseSensitive();
}
getNewLine() {
switch (this.options.newLine) {
case ts.NewLineKind.CarriageReturnLineFeed:
return '\r\n';
case ts.NewLineKind.LineFeed:
return '\n';
default:
return os__namespace.EOL;
}
}
fileExists(fileName) {
const absPath = this.fs.resolve(fileName);
return this.fs.exists(absPath) && this.fs.stat(absPath).isFile();
}
readFile(fileName) {
const absPath = this.fs.resolve(fileName);
if (!this.fileExists(absPath)) {
return undefined;
}
return this.fs.readFile(absPath);
}
realpath(path) {
return this.fs.realpath(this.fs.resolve(path));
}
}
const LogicalProjectPath = {
/**
* Get the relative path between two `LogicalProjectPath`s.
*
* This will return a `PathSegment` which would be a valid module specifier to use in `from` when
* importing from `to`.
*/
relativePathBetween: function (from, to) {
const relativePath = relative(dirname(resolve(from)), resolve(to));
return toRelativeImport(relativePath);
},
};
/**
* A utility class which can translate absolute paths to source files into logical paths in
* TypeScript's logical file system, based on the root directories of the project.
*/
class LogicalFileSystem {
compilerHost;
/**
* The root directories of the project, sorted with the longest path first.
*/
rootDirs;
/**
* The same root directories as `rootDirs` but with each one converted to its
* canonical form for matching in case-insensitive file-systems.
*/
canonicalRootDirs;
/**
* A cache of file paths to project paths, because computation of these paths is slightly
* expensive.
*/
cache = new Map();
constructor(rootDirs, compilerHost) {
this.compilerHost = compilerHost;
// Make a copy and sort it by length in reverse order (longest first). This speeds up lookups,
// since there's no need to keep going through the array once a match is found.
this.rootDirs = rootDirs.concat([]).sort((a, b) => b.length - a.length);
this.canonicalRootDirs = this.rootDirs.map((dir) => this.compilerHost.getCanonicalFileName(dir));
}
/**
* Get the logical path in the project of a `ts.SourceFile`.
*
* This method is provided as a convenient alternative to calling
* `logicalPathOfFile(absoluteFromSourceFile(sf))`.
*/
logicalPathOfSf(sf) {
return this.logicalPathOfFile(absoluteFromSourceFile(sf));
}
/**
* Get the logical path in the project of a source file.
*
* @returns A `LogicalProjectPath` to the source file, or `null` if the source file is not in any
* of the TS project's root directories.
*/
logicalPathOfFile(physicalFile) {
if (!this.cache.has(physicalFile)) {
const canonicalFilePath = this.compilerHost.getCanonicalFileName(physicalFile);
let logicalFile = null;
for (let i = 0; i < this.rootDirs.length; i++) {
const rootDir = this.rootDirs[i];
const canonicalRootDir = this.canonicalRootDirs[i];
if (isWithinBasePath$1(canonicalRootDir, canonicalFilePath)) {
// Note that we match against canonical paths but then create the logical path from
// original paths.
logicalFile = this.createLogicalProjectPath(physicalFile, rootDir);
// The logical project does not include any special "node_modules" nested directories.
if (logicalFile.indexOf('/node_modules/') !== -1) {
logicalFile = null;
}
else {
break;
}
}
}
this.cache.set(physicalFile, logicalFile);
}
return this.cache.get(physicalFile);
}
createLogicalProjectPath(file, rootDir) {
const logicalPath = stripExtension(file.slice(rootDir.length));
return (logicalPath.startsWith('/') ? logicalPath : '/' + logicalPath);
}
}
/**
* Is the `path` a descendant of the `base`?
* E.g. `foo/bar/zee` is within `foo/bar` but not within `foo/car`.
*/
function isWithinBasePath$1(base, path) {
return isLocalRelativePath(relative(base, path));
}
/**
* @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
*/
/// <reference types="node" />
/**
* A wrapper around the Node.js file-system that supports path manipulation.
*/
class NodeJSPathManipulation {
pwd() {
return this.normalize(process.cwd());
}
chdir(dir) {
process.chdir(dir);
}
resolve(...paths) {
return this.normalize(p__namespace.resolve(...paths));
}
dirname(file) {
return this.normalize(p__namespace.dirname(file));
}
join(basePath, ...paths) {
return this.normalize(p__namespace.join(basePath, ...paths));
}
isRoot(path) {
return this.dirname(path) === this.normalize(path);
}
isRooted(path) {
return p__namespace.isAbsolute(path);
}
relative(from, to) {
return this.normalize(p__namespace.relative(from, to));
}
basename(filePath, extension) {
return p__namespace.basename(filePath, extension);
}
extname(path) {
return p__namespace.extname(path);
}
normalize(path) {
// Convert backslashes to forward slashes
return path.replace(/\\/g, '/');
}
}
// G3-ESM-MARKER: G3 uses CommonJS, but externally everything in ESM.
// CommonJS/ESM interop for determining the current file name and containing dir.
const isCommonJS = typeof __filename !== 'undefined';
const currentFileUrl = isCommonJS ? null : new URL(module.uri, document.baseURI).href;
// Note, when this code loads in the browser, `url` may be an empty `{}` due to the Closure shims.
const currentFileName = isCommonJS
? __filename
: (url__namespace.fileURLToPath?.(currentFileUrl) ?? null);
/**
* A wrapper around the Node.js file-system that supports readonly operations and path manipulation.
*/
class NodeJSReadonlyFileSystem extends NodeJSPathManipulation {
_caseSensitive = undefined;
isCaseSensitive() {
if (this._caseSensitive === undefined) {
// Note the use of the real file-system is intentional:
// `this.exists()` relies upon `isCaseSensitive()` so that would cause an infinite recursion.
this._caseSensitive =
currentFileName !== null
? !fs$1.existsSync(this.normalize(toggleCase(currentFileName)))
: true;
}
return this._caseSensitive;
}
exists(path) {
return fs$1.existsSync(path);
}
readFile(path) {
return fs$1.readFileSync(path, 'utf8');
}
readFileBuffer(path) {
// TODO: go/ts59upgrade - Remove the suppression after TS 5.9.2 upgrade
// TS2322: Type 'Buffer' is not assignable to type 'Uint8Array<ArrayBufferLike>'.
// @ts-ignore
return fs$1.readFileSync(path);
}
readdir(path) {
return fs$1.readdirSync(path);
}
lstat(path) {
return fs$1.lstatSync(path);
}
stat(path) {
return fs$1.statSync(path);
}
realpath(path) {
return this.resolve(fs$1.realpathSync(path));
}
getDefaultLibLocation() {
// G3-ESM-MARKER: G3 uses CommonJS, but externally everything in ESM.
const requireFn = isCommonJS ? require : module$1.createRequire(currentFileUrl);
return this.resolve(requireFn.resolve('typescript'), '..');
}
}
/**
* A wrapper around the Node.js file-system (i.e. the `fs` package).
*/
class NodeJSFileSystem extends NodeJSReadonlyFileSystem {
writeFile(path, data, exclusive = false) {
fs$1.writeFileSync(path, data, exclusive ? { flag: 'wx' } : undefined);
}
removeFile(path) {
fs$1.unlinkSync(path);
}
symlink(target, path) {
fs$1.symlinkSync(target, path);
}
copyFile(from, to) {
fs$1.copyFileSync(from, to);
}
moveFile(from, to) {
fs$1.renameSync(from, to);
}
ensureDir(path) {
fs$1.mkdirSync(path, { recursive: true });
}
removeDeep(path) {
fs$1.rmdirSync(path, { recursive: true });
}
}
/**
* Toggle the case of each character in a string.
*/
function toggleCase(str) {
return str.replace(/\w/g, (ch) => ch.toUpperCase() === ch ? ch.toLowerCase() : ch.toUpperCase());
}
/**
* @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
*/
// We use TypeScript's native `ts.matchFiles` utility for the virtual file systems
// and their TypeScript compiler host `readDirectory` implementation. TypeScript's
// function implements complex logic for matching files with respect to root
// directory, extensions, excludes, includes etc. The function is currently
// internal but we can use it as the API most likely will not change any time soon,
// nor does it seem like this is being made public any time soon.
// Related issue for tracking: https://github.com/microsoft/TypeScript/issues/13793.
/**
* Creates a {@link ts.CompilerHost#readDirectory} implementation function,
* that leverages the specified file system (that may be e.g. virtual).
*/
function createFileSystemTsReadDirectoryFn(fs) {
if (ts.matchFiles === undefined) {
throw Error('Unable to read directory in configured file system. This means that ' +
'TypeScript changed its file matching internals.\n\nPlease consider downgrading your ' +
'TypeScript version, and report an issue in the Angular framework repository.');
}
const matchFilesFn = ts.matchFiles.bind(ts);
return (rootDir, extensions, excludes, includes, depth) => {
const directoryExists = (p) => {
const resolvedPath = fs.resolve(p);
return fs.exists(resolvedPath) && fs.stat(resolvedPath).isDirectory();
};
return matchFilesFn(rootDir, extensions, excludes, includes, fs.isCaseSensitive(), fs.pwd(), depth, (p) => {
const resolvedPath = fs.resolve(p);
// TS also gracefully returns an empty file set.
if (!directoryExists(resolvedPath)) {
return { directories: [], files: [] };
}
const children = fs.readdir(resolvedPath);
const files = [];
const directories = [];
for (const child of children) {
try {
if (fs.stat(fs.join(resolvedPath, child))?.isDirectory()) {
directories.push(child);
}
else {
files.push(child);
}
}
catch (error) {
if (error instanceof Error && error.message.includes('ENOENT')) {
// may be a dangling link or other file system error, ignore
continue;
}
throw error;
}
}
return { files, directories };
}, (p) => fs.resolve(p), (p) => directoryExists(p));
};
}
/**
* @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
*/
const _SELECTOR_REGEXP = new RegExp('(\\:not\\()|' + // 1: ":not("
'(([\\.\\#]?)[-\\w]+)|' + // 2: "tag"; 3: "."/"#";
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
// 4: attribute; 5: attribute_string; 6: attribute_value
'(?:\\[([-.\\w*\\\\$]+)(?:=(["\']?)([^\\]"\']*)\\5)?\\])|' + // "[name]", "[name=value]",
// "[name="value"]",
// "[name='value']"
'(\\))|' + // 7: ")"
'(\\s*,\\s*)', // 8: ","
'g');
/**
* A css selector contains an element name,
* css classes and attribute/value pairs with the purpose
* of selecting subsets out of them.
*/
class CssSelector {
element = null;
classNames = [];
/**
* The selectors are encoded in pairs where:
* - even locations are attribute names
* - odd locations are attribute values.
*
* Example:
* Selector: `[key1=value1][key2]` would parse to:
* ```
* ['key1', 'value1', 'key2', '']
* ```
*/
attrs = [];
notSelectors = [];
static parse(selector) {
const results = [];
const _addResult = (res, cssSel) => {
if (cssSel.notSelectors.length > 0 &&
!cssSel.element &&
cssSel.classNames.length == 0 &&
cssSel.attrs.length == 0) {
cssSel.element = '*';
}
res.push(cssSel);
};
let cssSelector = new CssSelector();
let match;
let current = cssSelector;
let inNot = false;
_SELECTOR_REGEXP.lastIndex = 0;
while ((match = _SELECTOR_REGEXP.exec(selector))) {
if (match[1 /* SelectorRegexp.NOT */]) {
if (inNot) {
throw new Error('Nesting :not in a selector is not allowed');
}
inNot = true;
current = new CssSelector();
cssSelector.notSelectors.push(current);
}
const tag = match[2 /* SelectorRegexp.TAG */];
if (tag) {
const prefix = match[3 /* SelectorRegexp.PREFIX */];
if (prefix === '#') {
// #hash
current.addAttribute('id', tag.slice(1));
}
else if (prefix === '.') {
// Class
current.addClassName(tag.slice(1));
}
else {
// Element
current.setElement(tag);
}
}
const attribute = match[4 /* SelectorRegexp.ATTRIBUTE */];
if (attribute) {
current.addAttribute(current.unescapeAttribute(attribute), match[6 /* SelectorRegexp.ATTRIBUTE_VALUE */]);
}
if (match[7 /* SelectorRegexp.NOT_END */]) {
inNot = false;
current = cssSelector;
}
if (match[8 /* SelectorRegexp.SEPARATOR */]) {
if (inNot) {
throw new Error('Multiple selectors in :not are not supported');
}
_addResult(results, cssSelector);
cssSelector = current = new CssSelector();
}
}
_addResult(results, cssSelector);
return results;
}
/**
* Unescape `\$` sequences from the CSS attribute selector.
*
* This is needed because `$` can have a special meaning in CSS selectors,
* but we might want to match an attribute that contains `$`.
* [MDN web link for more
* info](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors).
* @param attr the attribute to unescape.
* @returns the unescaped string.
*/
unescapeAttribute(attr) {
let result = '';
let escaping = false;
for (let i = 0; i < attr.length; i++) {
const char = attr.charAt(i);
if (char === '\\') {
escaping = true;
continue;
}
if (char === '$' && !escaping) {
throw new Error(`Error in attribute selector "${attr}". ` +
`Unescaped "$" is not supported. Please escape with "\\$".`);
}
escaping = false;
result += char;
}
return result;
}
/**
* Escape `$` sequences from the CSS attribute selector.
*
* This is needed because `$` can have a special meaning in CSS selectors,
* with this method we are escaping `$` with `\$'.
* [MDN web link for more
* info](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors).
* @param attr the attribute to escape.
* @returns the escaped string.
*/
escapeAttribute(attr) {
return attr.replace(/\\/g, '\\\\').replace(/\$/g, '\\$');
}
isElementSelector() {
return (this.hasElementSelector() &&
this.classNames.length == 0 &&
this.attrs.length == 0 &&
this.notSelectors.length === 0);
}
hasElementSelector() {
return !!this.element;
}
setElement(element = null) {
this.element = element;
}
getAttrs() {
const result = [];
if (this.classNames.length > 0) {
result.push('class', this.classNames.join(' '));
}
return result.concat(this.attrs);
}
addAttribute(name, value = '') {
this.attrs.push(name, (value && value.toLowerCase()) || '');
}
addClassName(name) {
this.classNames.push(name.toLowerCase());
}
toString() {
let res = this.element || '';
if (this.classNames) {
this.classNames.forEach((klass) => (res += `.${klass}`));
}
if (this.attrs) {
for (let i = 0; i < this.attrs.length; i += 2) {
const name = this.escapeAttribute(this.attrs[i]);
const value = this.attrs[i + 1];
res += `[${name}${value ? '=' + value : ''}]`;
}
}
this.notSelectors.forEach((notSelector) => (res += `:not(${notSelector})`));
return res;
}
}
/**
* Reads a list of CssSelectors and allows to calculate which ones
* are contained in a given CssSelector.
*/
class SelectorMatcher {
static createNotMatcher(notSelectors) {
const notMatcher = new SelectorMatcher();
notMatcher.addSelectables(notSelectors, null);
return notMatcher;
}
_elementMap = new Map();
_elementPartialMap = new Map();
_classMap = new Map();
_classPartialMap = new Map();
_attrValueMap = new Map();
_attrValuePartialMap = new Map();
_listContexts = [];
addSelectables(cssSelectors, callbackCtxt) {
let listContext = null;
if (cssSelectors.length > 1) {
listContext = new SelectorListContext(cssSelectors);
this._listContexts.push(listContext);
}
for (let i = 0; i < cssSelectors.length; i++) {
this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
}
}
/**
* Add an object that can be found later on by calling `match`.
* @param cssSelector A css selector
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
*/
_addSelectable(cssSelector, callbackCtxt, listContext) {
let matcher = this;
const element = cssSelector.element;
const classNames = cssSelector.classNames;
const attrs = cssSelector.attrs;
const selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
if (element) {
const isTerminal = attrs.length === 0 && classNames.length === 0;
if (isTerminal) {
this._addTerminal(matcher._elementMap, element, selectable);
}
else {
matcher = this._addPartial(matcher._elementPartialMap, element);
}
}
if (classNames) {
for (let i = 0; i < classNames.length; i++) {
const isTerminal = attrs.length === 0 && i === classNames.length - 1;
const className = classNames[i];
if (isTerminal) {
this._addTerminal(matcher._classMap, className, selectable);
}
else {
matcher = this._addPartial(matcher._classPartialMap, className);
}
}
}
if (attrs) {
for (let i = 0; i < attrs.length; i += 2) {
const isTerminal = i === attrs.length - 2;
const name = attrs[i];
const value = attrs[i + 1];
if (isTerminal) {
const terminalMap = matcher._attrValueMap;
let terminalValuesMap = terminalMap.get(name);
if (!terminalValuesMap) {
terminalValuesMap = new Map();
terminalMap.set(name, terminalValuesMap);
}
this._addTerminal(terminalValuesMap, value, selectable);
}
else {
const partialMap = matcher._attrValuePartialMap;
let partialValuesMap = partialMap.get(name);
if (!partialValuesMap) {
partialValuesMap = new Map();
partialMap.set(name, partialValuesMap);
}
matcher = this._addPartial(partialValuesMap, value);
}
}
}
}
_addTerminal(map, name, selectable) {
let terminalList = map.get(name);
if (!terminalList) {
terminalList = [];
map.set(name, terminalList);
}
terminalList.push(selectable);
}
_addPartial(map, name) {
let matcher = map.get(name);
if (!matcher) {
matcher = new SelectorMatcher();
map.set(name, matcher);
}
return matcher;
}
/**
* Find the objects that have been added via `addSelectable`
* whose css selector is contained in the given css selector.
* @param cssSelector A css selector
* @param matchedCallback This callback will be called with the object handed into `addSelectable`
* @return boolean true if a match was found
*/
match(cssSelector, matchedCallback) {
let result = false;
const element = cssSelector.element;
const classNames = cssSelector.classNames;
const attrs = cssSelector.attrs;
for (let i = 0; i < this._listContexts.length; i++) {
this._listContexts[i].alreadyMatched = false;
}
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
result =
this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result;
if (classNames) {
for (let i = 0; i < classNames.length; i++) {
const className = classNames[i];
result =
this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
result =
this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback) ||
result;
}
}
if (attrs) {
for (let i = 0; i < attrs.length; i += 2) {
const name = attrs[i];
const value = attrs[i + 1];
const terminalValuesMap = this._attrValueMap.get(name);
if (value) {
result =
this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result;
}
result =
this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result;
const partialValuesMap = this._attrValuePartialMap.get(name);
if (value) {
result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result;
}
result =
this._matchPartial(partialValuesMap, value, cssSelector, matchedCallback) || result;
}
}
return result;
}
/** @internal */
_matchTerminal(map, name, cssSelector, matchedCallback) {
if (!map || typeof name !== 'string') {
return false;
}
let selectables = map.get(name) || [];
const starSelectables = map.get('*');
if (starSelectables) {
selectables = selectables.concat(starSelectables);
}
if (selectables.length === 0) {
return false;
}
let selectable;
let result = false;
for (let i = 0; i < selectables.length; i++) {
selectable = selectables[i];
result = selectable.finalize(cssSelector, matchedCallback) || result;
}
return result;
}
/** @internal */
_matchPartial(map, name, cssSelector, matchedCallback) {
if (!map || typeof name !== 'string') {
return false;
}
const nestedSelector = map.get(name);
if (!nestedSelector) {
return false;
}
// TODO(perf): get rid of recursion and measure again
// TODO(perf): don't pass the whole selector into the recursion,
// but only the not processed parts
return nestedSelector.match(cssSelector, matchedCallback);
}
}
class SelectorListContext {
selectors;
alreadyMatched = false;
constructor(selectors) {
this.selectors = selectors;
}
}
// Store context to pass back selector and context when a selector is matched
class SelectorContext {
selector;
cbContext;
listContext;
notSelectors;
constructor(selector, cbContext, listContext) {
this.selector = selector;
this.cbContext = cbContext;
this.listContext = listContext;
this.notSelectors = selector.notSelectors;
}
finalize(cssSelector, callback) {
let result = true;
if (this.notSelectors.length > 0 && (!this.listContext || !this.listContext.alreadyMatched)) {
const notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
result = !notMatcher.match(cssSelector, null);
}
if (result && callback && (!this.listContext || !this.listContext.alreadyMatched)) {
if (this.listContext) {
this.listContext.alreadyMatched = true;
}
callback(this.selector, this.cbContext);
}
return result;
}
}
class SelectorlessMatcher {
registry;
constructor(registry) {
this.registry = registry;
}
match(name) {
return this.registry.has(name) ? this.registry.get(name) : [];
}
}
/**
* @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
*/
/**
* A SecurityContext marks a location that has dangerous security implications, e.g. a DOM property
* like `innerHTML` that could cause Cross Site Scripting (XSS) security bugs when improperly
* handled.
*
* See DomSanitizer for more details on security in Angular applications.
*
* @publicApi
*/
var SecurityContext;
(function (SecurityContext) {
SecurityContext[SecurityContext["NONE"] = 0] = "NONE";
SecurityContext[SecurityContext["HTML"] = 1] = "HTML";
SecurityContext[SecurityContext["STYLE"] = 2] = "STYLE";
SecurityContext[SecurityContext["SCRIPT"] = 3] = "SCRIPT";
SecurityContext[SecurityContext["URL"] = 4] = "URL";
SecurityContext[SecurityContext["RESOURCE_URL"] = 5] = "RESOURCE_URL";
SecurityContext[SecurityContext["ATTRIBUTE_NO_BINDING"] = 6] = "ATTRIBUTE_NO_BINDING";
})(SecurityContext || (SecurityContext = {}));
let _SECURITY_SCHEMA;
const SVG_NAMESPACE$1 = 'svg';
const MATH_ML_NAMESPACE$1 = 'math';
const NO_NAMESPACE = '';
const MATCH_ALL_ELEMENTS = '*';
const createNullObj = () => Object.create(null);
/**
* @remarks Keep is a copy of DOM Security Schema.
* @see [SECURITY_SCHEMA](../../../compiler/src/schema/dom_security_schema.ts)
*/
function SECURITY_SCHEMA() {
if (_SECURITY_SCHEMA) {
return _SECURITY_SCHEMA;
}
_SECURITY_SCHEMA = createNullObj();
// Case is insignificant below, all element and attribute names are lower-cased for lookup.
registerContext(SecurityContext.HTML, /** Namespace */ undefined, [
['iframe', ['srcdoc']],
['*', ['innerHTML', 'outerHTML']],
]);
registerContext(SecurityContext.STYLE, /** Namespace */ undefined, [['*', ['style']]]);
// NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them.
registerContext(SecurityContext.URL, /** Namespace */ undefined, [
['*', ['formAction']],
['area', ['href']],
['a', ['href', 'xlink:href']],
['form', ['action']],
// The below two items are safe and should be removed but they require a G3 clean-up as a small number of tests fail.
['img', ['src']],
['video', ['src']],
]);
registerContext(SecurityContext.URL, MATH_ML_NAMESPACE$1, [
// MathML namespace
// https://crsrc.org/c/third_party/blink/renderer/core/sanitizer/sanitizer.cc;l=753-768;drc=b3eb16372dcd3317d65e9e0265015e322494edcd;bpv=1;bpt=1
['*', ['href', 'xlink:href']],
]);
registerContext(SecurityContext.RESOURCE_URL, /** Namespace */ undefined, [
['base', ['href']],
['embed', ['src']],
['frame', ['src']],
['iframe', ['src']],
['link', ['href']],
['object', ['codebase', 'data']],
]);
registerContext(SecurityContext.URL, SVG_NAMESPACE$1, [['a', ['href', 'xlink:href']]]);
// Keep this in sync with SECURITY_SENSITIVE_ELEMENTS in packages/core/src/sanitization/sanitization.ts
// The `unknown` elements refer to cases when we need to validate the input/binding in a directive (host bindings)
// and the directive can be applied to multiple different elements (with different tag names). In this case we generate
// a special instruction that an attribute might potentially be security-sensitive and defer the actual security check
// to runtime, when we apply that directive to a concrete elements, thus we can check the combination of tag+attribute
// against the set that requires sanitization.
// These are unsafe as `attributeName` can be `href` or `xlink:href`
// See: http://b/463880509#comment7
registerContext(SecurityContext.ATTRIBUTE_NO_BINDING, SVG_NAMESPACE$1, [
['animate', ['attributeName', 'values', 'to', 'from']],
['set', ['to', 'attributeName']],
['animateMotion', ['attributeName']],
['animateTransform', ['attributeName']],
]);
registerContext(SecurityContext.ATTRIBUTE_NO_BINDING, /** Namespace */ undefined, [
[
'unknown',
[
'attributeName',
'values',
'to',
'from',
'sandbox',
'allow',
'allowFullscreen',
'referrerPolicy',
'csp',
'fetchPriority',
'credentialless',
],
],
[
'iframe',
[
'sandbox',
'allow',
'allowFullscreen',
'referrerPolicy',
'csp',
'fetchPriority',
'credentialless',
],
],
]);
return _SECURITY_SCHEMA;
}
function registerContext(ctx, namespace, specs) {
const nsKey = namespace ?? NO_NAMESPACE;
for (const [element, attributeNames] of specs) {
const tagName = element.toLowerCase();
for (const attr of attributeNames) {
const attrLower = attr.toLowerCase();
const attrSchema = (_SECURITY_SCHEMA[attrLower] ??= createNullObj());
const nsSchema = (attrSchema[nsKey] ??= createNullObj());
nsSchema[tagName] = ctx;
}
}
}
/**
* Checks the SecurityContext for a given tag and property.
* @param tagName The tag name (e.g. 'div', or 'a')
* @param propName The property or attribute name
* @param namespace The namespace of the element, if any (e.g. 'svg' or 'math')
*/
function checkSecurityContext(tagName, propName, namespace) {
const securitySchema = SECURITY_SCHEMA();
const attrSchema = securitySchema[propName.toLowerCase()];
if (!attrSchema) {
return SecurityContext.NONE;
}
const tagLower = tagName.toLowerCase();
let context;
if (namespace) {
const nsSchema = attrSchema[namespace];
if (nsSchema) {
context = nsSchema[tagLower] ?? nsSchema[MATCH_ALL_ELEMENTS];
}
}
if (context === undefined) {
const defaultSchema = attrSchema[NO_NAMESPACE];
if (defaultSchema) {
context = defaultSchema[tagLower] ?? defaultSchema[MATCH_ALL_ELEMENTS];
}
}
return context ?? SecurityContext.NONE;
}
/**
* @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
*/
// Attention:
// This file duplicates types and values from @angular/core
// so that we are able to make @angular/compiler independent of @angular/core.
// This is important to prevent a build cycle, as @angular/core needs to
// be compiled with the compiler.
// Stores the default value of `emitDistinctChangesOnly` when the `emitDistinctChangesOnly` is not
// explicitly set.
const emitDistinctChangesOnlyDefaultValue = true;
var ViewEncapsulation$1;
(function (ViewEncapsulation) {
ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None";
ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom";
ViewEncapsulation[ViewEncapsulation["ExperimentalIsolatedShadowDom"] = 4] = "ExperimentalIsolatedShadowDom";
})(ViewEncapsulation$1 || (ViewEncapsulation$1 = {}));
var ChangeDetectionStrategy;
(function (ChangeDetectionStrategy) {
ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush";
ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default";
// tslint:disable-next-line:no-duplicate-enum-values
ChangeDetectionStrategy[ChangeDetectionStrategy["Eager"] = 1] = "Eager";
})(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}));
/** Flags describing an input for a directive. */
var InputFlags;
(function (InputFlags) {
InputFlags[InputFlags["None"] = 0] = "None";
InputFlags[InputFlags["SignalBased"] = 1] = "SignalBased";
InputFlags[InputFlags["HasDecoratorInputTransform"] = 2] = "HasDecoratorInputTransform";
})(InputFlags || (InputFlags = {}));
const CUSTOM_ELEMENTS_SCHEMA = {
name: 'custom-elements',
};
const NO_ERRORS_SCHEMA = {
name: 'no-errors-schema',
};
var MissingTranslationStrategy;
(function (MissingTranslationStrategy) {
MissingTranslationStrategy[MissingTranslationStrategy["Error"] = 0] = "Error";
MissingTranslationStrategy[MissingTranslationStrategy["Warning"] = 1] = "Warning";
MissingTranslationStrategy[MissingTranslationStrategy["Ignore"] = 2] = "Ignore";
})(MissingTranslationStrategy || (MissingTranslationStrategy = {}));
function parserSelectorToSimpleSelector(selector) {
const classes = selector.classNames && selector.classNames.length
? [8 /* SelectorFlags.CLASS */, ...selector.classNames]
: [];
const elementName = selector.element && selector.element !== '*' ? selector.element : '';
return [elementName, ...selector.attrs, ...classes];
}
function parserSelectorToNegativeSelector(selector) {
const classes = selector.classNames && selector.classNames.length
? [8 /* SelectorFlags.CLASS */, ...selector.classNames]
: [];
if (selector.element) {
return [
1 /* SelectorFlags.NOT */ | 4 /* SelectorFlags.ELEMENT */,
selector.element,
...selector.attrs,
...classes,
];
}
else if (selector.attrs.length) {
return [1 /* SelectorFlags.NOT */ | 2 /* SelectorFlags.ATTRIBUTE */, ...selector.attrs, ...classes];
}
else {
return selector.classNames && selector