igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
772 lines (771 loc) • 35.8 kB
JavaScript
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BindingType = exports.UpdateChanges = exports.InputPropertyType = void 0;
const fs = require("fs");
const path = require("path");
const ts = require("typescript");
const tss = require("typescript/lib/tsserverlibrary");
const schema_1 = require("./schema");
const tsUtils_1 = require("./tsUtils");
const util_1 = require("./util");
const project_service_container_1 = require("./project-service-container");
const TSCONFIG_PATH = 'tsconfig.json';
var InputPropertyType;
(function (InputPropertyType) {
InputPropertyType["EVAL"] = "eval";
InputPropertyType["STRING"] = "string";
})(InputPropertyType || (exports.InputPropertyType = InputPropertyType = {}));
/* eslint-disable arrow-parens */
class UpdateChanges {
get shouldInvokeLS() {
return this._shouldInvokeLS;
}
set shouldInvokeLS(val) {
if (val === undefined || val === null) {
// call LS by default
this.shouldInvokeLS = true;
return;
}
this._shouldInvokeLS = val;
}
get projectService() {
const projectService = project_service_container_1.serviceContainer.projectService;
if (!project_service_container_1.serviceContainer.configured) {
this.configureForAngularLS(projectService);
project_service_container_1.serviceContainer.configured = true;
}
return projectService;
}
get serverHost() {
return project_service_container_1.serviceContainer.serverHost;
}
get templateFiles() {
if (!this._templateFiles.length) {
// https://github.com/angular/devkit/blob/master/packages/angular_devkit/schematics/src/tree/filesystem.ts
this.sourceDirsVisitor((fulPath, entry) => {
if (fulPath.endsWith('component.html')) {
this._templateFiles.push(entry.path);
}
});
}
return this._templateFiles;
}
get tsFiles() {
if (!this._tsFiles.length) {
this.sourceDirsVisitor((fulPath, entry) => {
if (fulPath.endsWith('.ts')) {
this._tsFiles.push(entry.path);
}
});
}
return this._tsFiles;
}
/** Sass (both .scss and .sass) files in the project being updated. */
get sassFiles() {
if (!this._sassFiles.length) {
// files can be outside the app prefix, so start from sourceRoot
// also ignore schematics `styleext` as Sass can be used regardless
const sourceDirs = (0, util_1.getProjects)(this.workspace).map(x => x.sourceRoot).filter(x => x);
this.sourceDirsVisitor((fulPath, entry) => {
if (fulPath.endsWith('.scss') || fulPath.endsWith('.sass')) {
this._sassFiles.push(entry.path);
}
}, sourceDirs);
}
return this._sassFiles;
}
get service() {
if (!this._service) {
this._service = (0, tsUtils_1.getLanguageService)(this.tsFiles, this.host);
}
return this._service;
}
get packageManager() {
if (!this._packageManager) {
this._packageManager = (0, util_1.getPackageManager)(this.host);
}
return this._packageManager;
}
/**
* Create a new base schematic to apply changes
*
* @param rootPath Root folder for the schematic to read configs, pass __dirname
*/
constructor(rootPath, host, context) {
this.rootPath = rootPath;
this.host = host;
this.context = context;
this.tsconfigPath = TSCONFIG_PATH;
this._shouldInvokeLS = true;
this.conditionFunctions = new Map();
this.valueTransforms = new Map();
this._templateFiles = [];
this._initialTsConfig = '';
this._tsFiles = [];
this._sassFiles = [];
this.workspace = (0, util_1.getWorkspace)(host);
this.sourcePaths = (0, util_1.getProjectPaths)(this.workspace);
this.selectorChanges = this.loadConfig('selectors.json');
this.classChanges = this.loadConfig('classes.json');
this.outputChanges = this.loadConfig('outputs.json');
this.inputChanges = this.loadConfig('inputs.json');
this.themeChanges = this.loadConfig('theme-changes.json');
this.importsChanges = this.loadConfig('imports.json');
this.membersChanges = this.loadConfig('members.json');
// update LS server host with the schematics tree:
this.serverHost.host = this.host;
}
/** Apply configured changes to the Host Tree */
applyChanges() {
const shouldInstallPkg = this.membersChanges && this.membersChanges.changes.length
&& !(0, util_1.canResolvePackage)(tsUtils_1.NG_LANG_SERVICE_PACKAGE_NAME);
if (shouldInstallPkg) {
this.context.logger.info(`Installing temporary migration dependencies via ${this.packageManager}.`);
// try and get an appropriate version of the package to install
let targetVersion = (0, util_1.getPackageVersion)(tsUtils_1.NG_CORE_PACKAGE_NAME) || 'latest';
if (targetVersion.startsWith('11')) {
// TODO: Temporary restrict 11 LS version, till update for new module loading
targetVersion = '11.0.0';
}
(0, util_1.tryInstallPackage)(this.context, this.packageManager, `${tsUtils_1.NG_LANG_SERVICE_PACKAGE_NAME}@${targetVersion}`);
}
this.updateTemplateFiles();
this.updateTsFiles();
if (this.shouldInvokeLS) {
this.updateMembers();
}
/** Sass files */
if (this.themeChanges && this.themeChanges.changes.length) {
for (const entryPath of this.sassFiles) {
this.updateThemeProps(entryPath);
this.updateSassVariables(entryPath);
this.updateSassFunctionsAndMixins(entryPath);
}
}
if (shouldInstallPkg) {
this.context.logger.info(`Cleaning up temporary migration dependencies.`);
(0, util_1.tryUninstallPackage)(this.context, this.packageManager, tsUtils_1.NG_LANG_SERVICE_PACKAGE_NAME);
}
// if tsconfig.json was patched, restore it
if (this._initialTsConfig !== '') {
this.host.overwrite(this.tsconfigPath, this._initialTsConfig);
}
}
/** Add condition function. */
addCondition(conditionName, callback) {
this.conditionFunctions.set(conditionName, callback);
}
addValueTransform(functionName, callback) {
this.valueTransforms.set(functionName, callback);
}
/** Path must be absolute. If calling externally, use this.getAbsolutePath */
getDefaultLanguageService(entryPath) {
const project = this.getDefaultProjectForFile(entryPath);
return project === null || project === void 0 ? void 0 : project.getLanguageService();
}
updateSelectors(entryPath) {
let fileContent = this.host.read(entryPath).toString();
let overwrite = false;
for (const change of this.selectorChanges.changes) {
let searchPttrn = change.type === 'component' ? '<' : '';
searchPttrn += change.selector;
if (fileContent.indexOf(searchPttrn) !== -1) {
fileContent = this.applySelectorChange(fileContent, change);
overwrite = true;
}
}
if (overwrite) {
this.host.overwrite(entryPath, fileContent);
}
}
applySelectorChange(fileContent, change) {
let regSource;
let replace;
switch (change.type) {
case 'component':
if (change.remove) {
regSource = String.raw `\<${change.selector}[\s\S]*?\<\/${change.selector}\>`;
replace = '';
}
else {
regSource = String.raw `\<(\/?)${change.selector}(?=[\s\>])`;
replace = `<$1${change.replaceWith}`;
}
break;
case 'directive':
if (change.remove) {
// Group match (\2) as variable as it looks like octal escape (error in strict)
regSource = String.raw `\s*?\[?${change.selector}\]?(=(["']).*?${'\\2'}(?=\s|\>))?`;
replace = '';
}
else {
regSource = change.selector;
replace = change.replaceWith;
}
break;
default:
break;
}
fileContent = fileContent.replace(new RegExp(regSource, 'g'), replace);
return fileContent;
}
updateClasses(entryPath) {
let fileContent = this.host.read(entryPath).toString();
const alreadyReplaced = new Set();
for (const change of this.classChanges.changes) {
if (fileContent.indexOf(change.name) !== -1) {
const positions = (0, tsUtils_1.getRenamePositions)(entryPath, change.name, this.service);
// loop backwards to preserve positions
for (let i = positions.length; i--;) {
const pos = positions[i];
// V.S. 18th May 2021: If several classes are renamed w/ the same import, erase them
// TODO: Refactor to make use of TSLS API instead of string replace
if (i === 0 && alreadyReplaced.has(change.replaceWith)) {
// only match the first trailing white space, right after the replace position
const trailingCommaWhiteSpace = new RegExp(/,([\s]*)(?=(\s}))/);
let afterReplace = fileContent.slice(pos.end);
const beforeReplace = fileContent.slice(0, pos.start);
const leadingComma = afterReplace[0] === ',' ? 1 : 0;
// recalculate if needed
afterReplace = !leadingComma ? afterReplace : fileContent.slice(pos.end + leadingComma);
const doubleSpaceReplace = beforeReplace[beforeReplace.length - 1].match(/\s/) !== null && afterReplace[0].match(/\s/) !== null ?
1 :
0;
fileContent = (fileContent.slice(0, pos.start - doubleSpaceReplace) +
'' +
afterReplace).replace(trailingCommaWhiteSpace, '');
}
else {
fileContent = fileContent.slice(0, pos.start) + change.replaceWith + fileContent.slice(pos.end);
}
}
if (positions.length) {
// using a set should be a lot quicker that getting position for renames of replace
alreadyReplaced.add(change.replaceWith);
this.host.overwrite(entryPath, fileContent);
}
}
}
}
updateBindings(entryPath, bindChanges, type = BindingType.Output) {
let fileContent = this.host.read(entryPath).toString();
let overwrite = false;
for (const change of bindChanges.changes) {
if (fileContent.indexOf(change.owner.selector) === -1 || fileContent.indexOf(change.name) === -1) {
continue;
}
let base;
let replace;
let searchPattern;
if (type === BindingType.Output) {
base = String.raw(templateObject_1 || (templateObject_1 = __makeTemplateObject(["(", void 0], ["\\(", "\\)=([\"'])(.*?)\\1"])), change.name);
replace = `(${change.replaceWith})=$1$2$1`;
}
else {
// Match both bound - [name] - and regular - name
base = String.raw(templateObject_2 || (templateObject_2 = __makeTemplateObject(["(s[?)", void 0], ["(\\s\\[?)", "(\\s*\\]?=)([\"'])(.*?)\\3"])), change.name);
replace = String.raw `$1${change.replaceWith}$2$3$4$3`;
}
let reg = new RegExp(base, 'g');
if (change.remove || change.moveBetweenElementTags) {
// Group match (\1) as variable as it looks like octal escape (error in strict)
reg = new RegExp(String.raw `\s*${base}(?=\s|\>)`, 'g');
replace = '';
}
switch (change.owner.type) {
case 'component':
searchPattern = String.raw `\<${change.owner.selector}(?=[\s\>])[^\>]*\>`;
break;
case 'directive':
searchPattern = String.raw `\<[^\>]*[\s\[]${change.owner.selector}[^\>]*\>`;
break;
}
const matches = fileContent.match(new RegExp(searchPattern, 'g'));
if (!matches) {
continue;
}
for (const match of matches) {
let replaceStatement = replace;
if (!this.areConditionsFulfilled(match, change.conditions, entryPath)) {
continue;
}
if (change.moveBetweenElementTags) {
const moveMatch = match.match(reg);
fileContent = this.copyPropertyValueBetweenElementTags(fileContent, match, moveMatch);
}
if (change.valueTransform) {
const regExpMatch = match.match(new RegExp(base));
const bindingType = regExpMatch && regExpMatch[1].endsWith('[') ? InputPropertyType.EVAL : InputPropertyType.STRING;
if (regExpMatch) {
const value = regExpMatch[4];
const transform = this.valueTransforms.get(change.valueTransform);
const args = { value, bindingType };
transform(args);
if (args.bindingType !== bindingType) {
replaceStatement = args.bindingType === InputPropertyType.EVAL ?
replaceStatement.replace(`$1`, `$1[`).replace(`$2`, `]$2`) :
replaceStatement.replace(`$1`, regExpMatch[1].replace('[', '')).replace('$2', regExpMatch[2].replace(']', ''));
}
replaceStatement = replaceStatement.replace('$4', args.value);
}
}
fileContent = fileContent.replace(match, match.replace(reg, replaceStatement));
}
overwrite = true;
}
if (overwrite) {
this.host.overwrite(entryPath, fileContent);
}
}
updateThemeProps(entryPath) {
var _a;
let fileContent = this.host.read(entryPath).toString();
let overwrite = false;
for (const change of this.themeChanges.changes) {
if (change.type !== schema_1.ThemeType.Property) {
continue;
}
if (fileContent.indexOf(change.owner) !== -1) {
/** owner-func:( * ); */
const searchPattern = String.raw `${change.owner}\([\s\S]+?\);`;
const matches = fileContent.match(new RegExp(searchPattern, 'g'));
if (!matches) {
continue;
}
for (const match of matches) {
if (match.indexOf(change.name) !== -1) {
const name = change.name.replace('$', '\\$');
const replaceWith = (_a = change.replaceWith) === null || _a === void 0 ? void 0 : _a.replace('$', '\\$');
const reg = new RegExp(String.raw `^\s*${name}:`);
const existing = new RegExp(String.raw `${replaceWith}:`);
const opening = `${change.owner}(`;
const closing = /\s*\);$/.exec(match).pop();
const body = match.substr(opening.length, match.length - opening.length - closing.length);
let params = this.splitFunctionProps(body);
params = params.reduce((arr, param) => {
if (reg.test(param)) {
const duplicate = !!replaceWith && arr.some(p => existing.test(p));
if (!change.remove && !duplicate) {
arr.push(param.replace(change.name, change.replaceWith));
}
}
else {
arr.push(param);
}
return arr;
}, []);
fileContent = fileContent.replace(match, opening + params.join(',') + closing);
overwrite = true;
}
}
}
}
if (overwrite) {
this.host.overwrite(entryPath, fileContent);
}
}
isNamedArgument(fileContent, i, occurrences, change) {
const openingBrackets = [];
const closingBrackets = [];
if (fileContent[(occurrences[i] + change.name.length)] !== ':'
|| (fileContent[(occurrences[i] + change.name.length)] === ' '
&& fileContent[(occurrences[i] + change.name.length) + 1] === ':')) {
return false;
}
for (let j = occurrences[i]; j >= 0; j--) {
if (fileContent[j] === ')') {
closingBrackets.push(fileContent[j]);
}
else if (fileContent[j] === '(') {
openingBrackets.push(fileContent[j]);
}
}
return openingBrackets.length !== closingBrackets.length;
}
updateSassVariables(entryPath) {
let fileContent = this.host.read(entryPath).toString();
let overwrite = false;
const allowedStartCharacters = new RegExp(/(:|,)\s?/, 'g');
// eslint-disable-next-line no-control-regex
const allowedEndCharacters = new RegExp('[;),: \r\n]', 'g');
for (const change of this.themeChanges.changes) {
if (change.type !== schema_1.ThemeType.Variable) {
continue;
}
if (!('owner' in change)) {
const occurrences = (0, tsUtils_1.findMatches)(fileContent, change.name);
for (let i = occurrences.length - 1; i >= 0; i--) {
const allowedStartEnd = fileContent[occurrences[i] - 1].match(allowedStartCharacters)
|| fileContent[(occurrences[i] + change.name.length)].match(allowedEndCharacters);
if (allowedStartEnd && !this.isNamedArgument(fileContent, i, occurrences, change)) {
fileContent = (0, util_1.replaceMatch)(fileContent, change.name, change.replaceWith, occurrences[i]);
overwrite = true;
}
}
}
}
if (overwrite) {
this.host.overwrite(entryPath, fileContent);
}
}
updateSassFunctionsAndMixins(entryPath) {
const aliases = this.getAliases(entryPath);
let fileContent = this.host.read(entryPath).toString();
let overwrite = false;
for (const change of this.themeChanges.changes) {
if (change.type !== schema_1.ThemeType.Function && change.type !== schema_1.ThemeType.Mixin) {
continue;
}
let occurrences = [];
if (aliases.length > 0 && !aliases.includes('*')) {
aliases.forEach(a => occurrences = occurrences.concat((0, tsUtils_1.findMatches)(fileContent, a + '.' + change.name)));
if (occurrences.length > 0) {
({ overwrite, fileContent } = this.tryReplaceScssFunctionWithAlias(occurrences, aliases, fileContent, change, overwrite));
continue;
}
}
occurrences = (0, tsUtils_1.findMatches)(fileContent, change.name);
if (occurrences.length > 0) {
({ overwrite, fileContent } = this.tryReplaceScssFunction(occurrences, fileContent, change, overwrite));
}
}
if (overwrite) {
this.host.overwrite(entryPath, fileContent);
}
}
getAliases(entryPath) {
const fileContent = this.host.read(entryPath).toString();
// B.P. 18/05/22 #11577 - Use RegEx to distinguish themed imports.
const matchers = [
/@use(\s+)('|")igniteui-angular\/theming\2\1as\1(\w+)/g,
/@use(\s+)('|")igniteui-angular\/theme\2\1as\1(\w+)/g,
/@use(\s+)('|")igniteui-angular\/lib\/core\/styles\/themes\/index\2\1as\1(\w+)/g
];
const aliases = [];
matchers.forEach(m => {
const match = m.exec(fileContent);
if (match) {
aliases.push(match[3]); // access the captured alias
}
});
return aliases;
}
updateImports(entryPath) {
let fileContent = this.host.read(entryPath).toString();
let overwrite = false;
for (const change of this.importsChanges.changes) {
if (fileContent.indexOf(change.name) === -1) {
continue;
}
const replace = (0, util_1.escapeRegExp)(change.replaceWith);
const base = (0, util_1.escapeRegExp)(change.name);
const reg = new RegExp(base, 'g');
fileContent = fileContent.replace(reg, replace);
overwrite = true;
}
if (overwrite) {
this.host.overwrite(entryPath, fileContent);
}
}
updateClassMembers(entryPath, memberChanges) {
let content = this.host.read(entryPath).toString();
const absPath = tss.server.toNormalizedPath(path.join(process.cwd(), entryPath));
// use the absolute path for ALL LS operations
// do not overwrite the entryPath, as Tree operations require relative paths
const changes = new Set();
let langServ;
for (const change of memberChanges.changes) {
if (!content.includes(change.member)) {
continue;
}
langServ = langServ || this.getDefaultLanguageService(absPath);
if (!langServ) {
return;
}
let matches;
if (entryPath.endsWith('.ts')) {
const source = langServ.getProgram().getSourceFile(absPath);
matches = (0, tsUtils_1.getIdentifierPositions)(source, change.member).map(x => x.start);
}
else {
matches = (0, tsUtils_1.findMatches)(content, `.${change.member}`).map(pos => pos + 1);
}
for (const matchPosition of matches) {
if ((0, tsUtils_1.isMemberIgniteUI)(change, langServ, absPath, matchPosition)) {
changes.add({ change, position: matchPosition });
}
}
}
const changesArr = Array.from(changes).sort((c, c1) => c.position - c1.position).reverse();
for (const fileChange of changesArr) {
content = (0, util_1.replaceMatch)(content, fileChange.change.member, fileChange.change.replaceWith, fileChange.position);
}
if (changes.size) {
this.host.overwrite(entryPath, content);
}
}
// TODO: combine both functions
tryReplaceScssFunctionWithAlias(occurrences, aliases, fileContent, change, overwrite) {
for (const alias of aliases) {
const aliasLength = alias.length + 1; // + 1 because of the dot - alias.member
for (let i = occurrences.length - 1; i >= 0; i--) {
const isOpenParenthesis = fileContent[occurrences[i] + aliasLength + change.name.length] === '(';
if (isOpenParenthesis) {
fileContent = (0, util_1.replaceMatch)(fileContent, change.name, change.replaceWith, occurrences[i] + aliasLength);
overwrite = true;
}
}
}
return { overwrite, fileContent };
}
tryReplaceScssFunction(occurrences, fileContent, change, overwrite) {
for (let i = occurrences.length - 1; i >= 0; i--) {
const isOpenParenthesis = fileContent[occurrences[i] + change.name.length] === '(';
if (isOpenParenthesis) {
fileContent = (0, util_1.replaceMatch)(fileContent, change.name, change.replaceWith, occurrences[i]);
overwrite = true;
}
}
return { overwrite, fileContent };
}
patchTsConfig() {
var _a, _b, _c, _d;
this.ensureTsConfigPath();
if (this.serverHost.fileExists(this.tsconfigPath)) {
let originalContent = '';
try {
originalContent = this.serverHost.readFile(this.tsconfigPath);
}
catch (_e) {
(_a = this.context) === null || _a === void 0 ? void 0 : _a.logger.warn(`Could not read ${this.tsconfigPath}. Some Angular Ivy features might be unavailable during migrations.`);
return;
}
let content;
// use ts parser as it handles jsonc-style files w/ comments
const result = ts.parseConfigFileTextToJson(this.tsconfigPath, originalContent);
if (!result.error) {
content = result.config;
}
else {
(_b = this.context) === null || _b === void 0 ? void 0 : _b.logger.warn(`Could not parse ${this.tsconfigPath}. Angular Ivy language service might be unavailable during migrations.`);
(_c = this.context) === null || _c === void 0 ? void 0 : _c.logger.warn(`Error:\n${result.error}`);
return;
}
if (!content.angularCompilerOptions) {
content.angularCompilerOptions = {};
}
if (!content.angularCompilerOptions.strictTemplates) {
(_d = this.context) === null || _d === void 0 ? void 0 : _d.logger.info(`Adding 'angularCompilerOptions.strictTemplates' to ${this.tsconfigPath} for migration run.`);
content.angularCompilerOptions.strictTemplates = true;
this.host.overwrite(this.tsconfigPath, JSON.stringify(content));
// store initial state and restore it once migrations are finished
this._initialTsConfig = originalContent;
}
}
}
ensureTsConfigPath() {
var _a, _b;
if (this.host.exists(this.tsconfigPath)) {
return;
}
// attempt to find a main tsconfig from workspace:
const wsProject = Object.values(this.workspace.projects)[0];
// technically could be per-project, but assuming there's at least one main tsconfig for IDE support
const projectConfig = (_b = (_a = wsProject.architect) === null || _a === void 0 ? void 0 : _a.build) === null || _b === void 0 ? void 0 : _b.options['tsConfig'];
if (!projectConfig || !this.host.exists(projectConfig)) {
return;
}
if (path.posix.basename(projectConfig) === TSCONFIG_PATH) {
// not project specific extended tsconfig, use directly
this.tsconfigPath = projectConfig;
return;
}
// look for base config through extends property
const result = ts.parseConfigFileTextToJson(projectConfig, this.serverHost.readFile(projectConfig));
if (!result.error && result.config.extends) {
this.tsconfigPath = path.posix.join(path.posix.dirname(projectConfig), result.config.extends);
}
}
loadConfig(configJson) {
const filePath = path.join(this.rootPath, 'changes', configJson);
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
}
}
areConditionsFulfilled(match, conditions, entryPath) {
if (conditions) {
for (const condition of conditions) {
if (this.conditionFunctions && this.conditionFunctions.has(condition)) {
const callback = this.conditionFunctions.get(condition);
if (callback && !callback(match, entryPath)) {
return false;
}
}
}
}
return true;
}
copyPropertyValueBetweenElementTags(fileContent, ownerMatch, propertyMatchArray) {
if (ownerMatch && propertyMatchArray && propertyMatchArray.length > 0) {
const propMatch = propertyMatchArray[0].trim();
const propValueMatch = propMatch.match(new RegExp(`=(["'])(.+?)${'\\1'}`));
if (propValueMatch && propValueMatch.length > 0) {
const propValue = propValueMatch[propValueMatch.length - 1];
if (propMatch.startsWith('[')) {
return fileContent.replace(ownerMatch, ownerMatch + `{{${propValue}}}`);
}
else {
return fileContent.replace(ownerMatch, ownerMatch + propValue);
}
}
}
return fileContent;
}
sourceDirsVisitor(visitor, dirs = this.sourcePaths) {
for (const sourcePath of dirs) {
const srcDir = this.host.getDir(sourcePath);
srcDir.visit(visitor);
}
}
/**
* Safe split by `','`, considering possible inner function calls. E.g.:
* ```
* prop: inner-func(),
* prop2: inner2(inner-param: 3, inner-param: inner-func(..))
* ```
*/
splitFunctionProps(body) {
const parts = [];
let lastIndex = 0;
let level = 0;
for (let i = 0; i < body.length; i++) {
const char = body[i];
switch (char) {
case '(':
level++;
break;
case ')':
level--;
break;
case ',':
if (!level) {
parts.push(body.substring(lastIndex, i));
lastIndex = i + 1;
}
break;
default:
break;
}
}
parts.push(body.substring(lastIndex));
return parts;
}
updateTemplateFiles() {
if (this.selectorChanges && this.selectorChanges.changes.length) {
for (const entryPath of this.templateFiles) {
this.updateSelectors(entryPath);
}
}
if (this.outputChanges && this.outputChanges.changes.length) {
// name change of output
for (const entryPath of this.templateFiles) {
this.updateBindings(entryPath, this.outputChanges);
}
}
if (this.inputChanges && this.inputChanges.changes.length) {
// name change of input
for (const entryPath of this.templateFiles) {
this.updateBindings(entryPath, this.inputChanges, BindingType.Input);
}
}
}
updateTsFiles() {
if (this.classChanges && this.classChanges.changes.length) {
// change class name
for (const entryPath of this.tsFiles) {
this.updateClasses(entryPath);
}
}
if (this.importsChanges && this.importsChanges.changes.length) {
// TODO: move logic to 7.0.2 migration
for (const entryPath of this.tsFiles) {
this.updateImports(entryPath);
}
}
}
updateMembers() {
if (this.membersChanges && this.membersChanges.changes.length) {
const dirs = [...this.templateFiles, ...this.tsFiles];
for (const entryPath of dirs) {
this.updateClassMembers(entryPath, this.membersChanges);
}
}
}
getDefaultProjectForFile(entryPath) {
var _a;
const scriptInfo = (_a = this.projectService) === null || _a === void 0 ? void 0 : _a.getOrCreateScriptInfoForNormalizedPath(tss.server.asNormalizedPath(entryPath), false);
if (!scriptInfo) {
return null;
}
this.projectService.openClientFile(scriptInfo.fileName);
const project = this.projectService.findProject(scriptInfo.containingProjects[0].getProjectName());
project.addMissingFileRoot(scriptInfo.fileName);
return project;
}
/**
* Force Angular service to compile project on initial load w/ configured project
* otherwise if the first compilation occurs on an HTML file the project won't have proper refs
* and no actual angular metadata will be resolved for the rest of the migration
*/
configureForAngularLS(projectService) {
// TODO: this pattern/issue might be obsolete
const mainRelPath = this.getWorkspaceProjectEntryPath();
if (!mainRelPath) {
return;
}
// patch TSConfig so it includes angularOptions.strictTemplates
// ivy ls requires this in order to function properly on templates
this.patchTsConfig();
const mainAbsPath = path.resolve(projectService.currentDirectory, mainRelPath);
const scriptInfo = projectService.getOrCreateScriptInfoForNormalizedPath(tss.server.toNormalizedPath(mainAbsPath), false);
projectService.openClientFile(scriptInfo.fileName);
try {
const project = projectService.findProject(scriptInfo.containingProjects[0].getProjectName());
project.getLanguageService().getSemanticDiagnostics(mainAbsPath);
}
catch (err) {
this.context.logger.warn("An error occurred during TypeScript project service setup. Some migrations relying on language services might not be applied.");
}
}
getWorkspaceProjectEntryPath() {
var _a, _b;
const projectKeys = Object.keys(this.workspace.projects);
if (!projectKeys.length) {
this.context.logger.info(`Could not resolve project from directory ${this.serverHost.getCurrentDirectory()}. Some migrations may not be applied.`);
return null;
}
// grab the first possible main path:
for (const key of projectKeys) {
const wsProject = this.workspace.projects[key];
// intentionally compare against string values of the enum to avoid hard import
if (wsProject.projectType == "application" && ((_b = (_a = wsProject.architect) === null || _a === void 0 ? void 0 : _a.build) === null || _b === void 0 ? void 0 : _b.options)) {
return wsProject.architect.build.options['browser'] || wsProject.architect.build.options['main'];
}
else if (wsProject.projectType == "library") {
// TODO: attempt to resolve from project ng-package.json or tsConfig
}
}
return null;
}
}
exports.UpdateChanges = UpdateChanges;
var BindingType;
(function (BindingType) {
BindingType[BindingType["Output"] = 0] = "Output";
BindingType[BindingType["Input"] = 1] = "Input";
})(BindingType || (exports.BindingType = BindingType = {}));
var templateObject_1, templateObject_2;
;