@clr/angular
Version:
Angular components for Clarity
187 lines • 9.3 kB
JavaScript
;
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.IMPORT_MIGRATION_CANDIDATES = void 0;
exports.transformImports = transformImports;
exports.migrateImports = migrateImports;
const import_replacements_1 = require("../replacements/import-replacements");
const symbol_replacements_1 = require("../replacements/symbol-replacements");
const file_visitor_1 = require("../utils/file-visitor");
const regexp_utils_1 = require("../utils/regexp-utils");
// ---------------------------------------------------------------------------
// Pre-compiled regex arrays — built once at module load, not per-file
// ---------------------------------------------------------------------------
// Sort import replacements by path length descending so more-specific paths
// (e.g. '@clr/angular/src/accordion/stepper') match before shorter prefixes.
const SORTED_IMPORT_REPLACEMENTS = [...import_replacements_1.IMPORT_REPLACEMENTS].sort((a, b) => b.oldModule.length - a.oldModule.length);
// Wildcard replacements: change the whole module path for all symbols.
const COMPILED_WILDCARD_IMPORT_REGEXES = SORTED_IMPORT_REPLACEMENTS.filter(r => r.oldSymbol === '*').map(r => ({
oldModule: r.oldModule,
newModule: r.newModule,
regex: new RegExp(`(from\\s+['"])${(0, regexp_utils_1.escapeRegExp)(r.oldModule)}(?=[/'"])`, 'g'),
}));
// Symbol-specific replacements: only the named symbol moves to the new module.
// Kept as plain data — splitSymbolImport() handles them at runtime to avoid
// the false positive where sibling symbols in the same import are dragged to
// the wrong module path.
const SYMBOL_SPECIFIC_IMPORTS = SORTED_IMPORT_REPLACEMENTS.filter(r => r.oldSymbol !== '*').map(r => ({
oldModule: r.oldModule,
newModule: r.newModule,
oldSymbol: r.oldSymbol,
newSymbol: r.newSymbol,
}));
// Only symbols with a non-empty new name are renamed here; removed symbols handled separately.
const COMPILED_SYMBOL_REPLACEMENTS = symbol_replacements_1.SYMBOL_REPLACEMENTS.filter(r => r.new).map(r => ({
...r,
// Ç (U+00C7) is not an ASCII word character so \b doesn't delimit it.
// Use explicit lookahead/lookbehind to avoid partial matches.
regex: r.old.startsWith('Ç')
? new RegExp(`(?<![A-Za-z0-9_$Ç])${(0, regexp_utils_1.escapeRegExp)(r.old)}(?![A-Za-z0-9_$])`, 'g')
: (0, regexp_utils_1.wordBoundaryRegex)(r.old),
}));
const REMOVED_SYMBOLS = symbol_replacements_1.SYMBOL_REPLACEMENTS.filter(r => r.new === '').map(r => r.old);
const REMOVED_SYMBOL_SOLE_IMPORT_REGEXES = REMOVED_SYMBOLS.map(sym => new RegExp(`^\\s*import\\s*\\{\\s*${(0, regexp_utils_1.escapeRegExp)(sym)}\\s*\\}\\s*from\\s*['"][^'"]+['"]\\s*;?\\s*$`, 'gm'));
const REMOVED_SYMBOL_LEADING_COMMA_REGEXES = REMOVED_SYMBOLS.map(sym => new RegExp(`\\b${(0, regexp_utils_1.escapeRegExp)(sym)}\\b\\s*,\\s*`, 'g'));
const REMOVED_SYMBOL_TRAILING_COMMA_REGEXES = REMOVED_SYMBOLS.map(sym => new RegExp(`\\s*,\\s*${(0, regexp_utils_1.escapeRegExp)(sym)}\\b`, 'g'));
const REMOVED_SYMBOL_USAGE_REGEXES = REMOVED_SYMBOLS.map(sym => ({
sym,
test: new RegExp(`\\b${(0, regexp_utils_1.escapeRegExp)(sym)}\\b`),
replace: new RegExp(`\\b${(0, regexp_utils_1.escapeRegExp)(sym)}\\b`, 'g'),
}));
// ---------------------------------------------------------------------------
// Fast-path candidate strings — if none present, skip the file entirely
// ---------------------------------------------------------------------------
exports.IMPORT_MIGRATION_CANDIDATES = [
...import_replacements_1.IMPORT_REPLACEMENTS.map(r => r.oldModule),
...symbol_replacements_1.SYMBOL_REPLACEMENTS.filter(r => r.new).map(r => r.old),
...REMOVED_SYMBOLS,
];
// ---------------------------------------------------------------------------
// Public pure transform — used by the unified .ts pass in index.ts
// ---------------------------------------------------------------------------
function transformImports(text) {
if (!exports.IMPORT_MIGRATION_CANDIDATES.some(c => text.includes(c))) {
return text;
}
text = replaceImportPaths(text);
text = replaceSymbols(text);
text = removeDeletedImports(text);
return text;
}
// ---------------------------------------------------------------------------
// Schematic Rule — kept for backwards compatibility; unified pass in index.ts
// is preferred for performance.
// ---------------------------------------------------------------------------
function migrateImports() {
return (tree, context) => {
context.logger.info(' Migrating TypeScript imports and symbols');
let scanCount = 0;
let fileCount = 0;
(0, file_visitor_1.visitFiles)(tree, '**/*.ts', filePath => {
scanCount++;
const content = tree.read(filePath);
if (!content) {
return;
}
const original = content.toString('utf-8');
const updated = transformImports(original);
if (updated !== original) {
tree.overwrite(filePath, updated);
fileCount++;
context.logger.info(` UPDATE ${filePath}`);
}
});
context.logger.info(` ${fileCount} of ${scanCount} file(s) updated.`);
};
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
function replaceImportPaths(text) {
// Wildcard replacements: move all symbols from oldModule to newModule.
for (const r of COMPILED_WILDCARD_IMPORT_REGEXES) {
if (!text.includes(r.oldModule)) {
continue;
}
r.regex.lastIndex = 0;
text = text.replace(r.regex, `$1${r.newModule}`);
}
// Symbol-specific replacements: extract one symbol to a new module, leaving
// any sibling symbols on the original module to prevent false positives.
for (const r of SYMBOL_SPECIFIC_IMPORTS) {
if (!text.includes(r.oldModule) || !text.includes(r.oldSymbol)) {
continue;
}
text = splitSymbolImport(text, r.oldSymbol, r.newSymbol, r.oldModule, r.newModule);
}
return text;
}
/**
* Extracts a single named symbol from an import and moves it to a new module.
*
* When the import contains sibling symbols, a second import for the remaining
* symbols on the original module is emitted. This prevents the false positive
* where all siblings are silently moved to the wrong module.
*
* Example (multi-symbol):
* import { FocusService, ClrFormsModule } from '@clr/angular/forms';
* →
* import { FormsFocusService } from '@clr/angular/forms/common';
* import { ClrFormsModule } from '@clr/angular/forms';
*/
function splitSymbolImport(text, oldSymbol, newSymbol, oldModule, newModule) {
const modEsc = (0, regexp_utils_1.escapeRegExp)(oldModule);
const symEsc = (0, regexp_utils_1.escapeRegExp)(oldSymbol);
// Compiled once per call, not inside the replace callback.
const symInListRe = new RegExp(`(?:^|,)\\s*${symEsc}\\s*(?:,|$)`);
const importRe = new RegExp(`([ \\t]*)import\\s*\\{([^}]*)\\}\\s*from\\s*(['"])${modEsc}\\3(\\s*;?)`, 'gm');
return text.replace(importRe, (match, indent, symbolList, quote, semi) => {
if (!symInListRe.test(symbolList)) {
return match;
}
const semiStr = semi.trim() || ';';
const remaining = symbolList
.split(',')
.map(s => s.trim())
.filter(s => s && s !== oldSymbol);
if (remaining.length === 0) {
return `${indent}import { ${newSymbol} } from ${quote}${newModule}${quote}${semiStr}`;
}
const newLine = `${indent}import { ${newSymbol} } from ${quote}${newModule}${quote}${semiStr}`;
const oldLine = `${indent}import { ${remaining.join(', ')} } from ${quote}${oldModule}${quote}${semiStr}`;
return `${newLine}\n${oldLine}`;
});
}
function replaceSymbols(text) {
for (const r of COMPILED_SYMBOL_REPLACEMENTS) {
if (!text.includes(r.old)) {
continue;
}
r.regex.lastIndex = 0;
text = text.replace(r.regex, r.new);
}
return text;
}
function removeDeletedImports(text) {
for (let i = 0; i < REMOVED_SYMBOLS.length; i++) {
const sym = REMOVED_SYMBOLS[i];
if (!text.includes(sym)) {
continue;
}
text = text.replace(REMOVED_SYMBOL_SOLE_IMPORT_REGEXES[i], '');
text = text.replace(REMOVED_SYMBOL_LEADING_COMMA_REGEXES[i], '');
text = text.replace(REMOVED_SYMBOL_TRAILING_COMMA_REGEXES[i], '');
const usage = REMOVED_SYMBOL_USAGE_REGEXES[i];
if (usage.test.test(text)) {
usage.replace.lastIndex = 0;
text = text.replace(usage.replace, `${sym} /* TODO: '${sym}' was removed in @clr/angular v18. Use standard KeyboardEvent.key values instead. */`);
}
}
return text;
}
//# sourceMappingURL=import-migration.js.map