/*! * @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 */ // In some case we know that we don't want to link a symbol // Example when there is a conflict between API entries and compiler features. // eg: "animate" is both an Animation API entry and an template instruction "animation.enter" // or "style" is a generic term but also an Animation API entry. const LINK_EXEMPT = new Set([ 'animate', 'animate.enter', 'animate.leave', 'style', 'readonly', 'disabled', 'hidden', 'state', 'group', 'animation', 'transition', 'trigger', 'group()', 'keyframes', '@keyframes', 'input', 'Event', 'form', 'type', 'Host', 'filter', ]); export function shouldLinkSymbol(symbol: string): boolean { return !LINK_EXEMPT.has(symbol); } // symbolName -> symbol info with moduleName and optional targetSymbol for aliases export type ApiEntries = Record; /** * Extracts the symbol name and property name from a symbol string. * eg: * foobar() => {symbolName: 'foobar', propName: null} * ApplicationRef.tick = > {symbolName: 'ApplicationRef', propName: 'tick'} * ApplicationRef.tick() = > {symbolName: 'ApplicationRef', propName: 'tick'} * @Component => {symbolName: 'Component', propName: null} */ export function extractFromSymbol(symbol: string): {propName: string | null; symbolName: string} { let propName: string | undefined; let symbolName = symbol; // we want to match functions when they have parentheses if (symbolName.endsWith('()')) { symbolName = symbolName.slice(0, -2); } if (symbolName.startsWith('@')) { symbolName = symbolName.slice(1); } if (symbolName.includes('#')) { [symbolName, propName] = symbolName.split('#'); } else if (symbolName.includes('.')) { [symbolName, propName] = symbolName.split('.'); } return {propName: propName ?? null, symbolName: symbolName}; } export function getSymbolUrl(symbol: string, apiEntries: ApiEntries): string | undefined { if (hasMoreThanOneDot(symbol) || !shouldLinkSymbol(symbol)) { return undefined; } const {symbolName, propName} = extractFromSymbol(symbol); // We don't want to match entries like "constructor" if (!Object.hasOwn(apiEntries, symbolName)) { return undefined; } const apiEntry = apiEntries[symbolName]; const moduleName = apiEntry.moduleName; const targetSymbol = apiEntry.targetSymbol ?? symbolName; return `/api/${moduleName}/${targetSymbol}${propName ? `#${propName}` : ''}`; } function hasMoreThanOneDot(str: string) { return str.split('.').length - 1 > 1; }