igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
362 lines (361 loc) • 20.3 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const UpdateChanges_1 = require("../common/UpdateChanges");
const util_1 = require("../common/util");
// use bare specifier to escape the schematics encapsulation for the dynamic import:
const import_helper_js_1 = require("igniteui-angular/migrations/common/import-helper.js");
const version = '12.0.0';
exports.default = (options) => (host, context) => __awaiter(void 0, void 0, void 0, function* () {
context.logger.info(`Applying migration for Ignite UI for Angular to version ${version}`);
const { HtmlParser } = yield (0, import_helper_js_1.nativeImport)('@angular/compiler');
// eslint-disable-next-line max-len
const UPDATE_NOTE = `<!--NOTE: This component has been updated by Infragistics migration: v${version}\nPlease check your template whether all bindings/event handlers are correct.-->\n`;
const COMPONENTS = [
{
component: 'igx-bottom-nav',
tags: ['igx-bottom-nav-item', 'igx-tab-panel', 'igx-tab'],
tabItem: 'igx-bottom-nav-item',
headerItem: 'igx-bottom-nav-header',
panelItem: 'igx-bottom-nav-content',
iconDirective: 'igxBottomNavHeaderIcon',
labelDirective: 'igxBottomNavHeaderLabel'
},
{
component: 'igx-tabs',
tags: ['igx-tabs-group', 'igx-tab-item'],
tabItem: 'igx-tab-item',
headerItem: 'igx-tab-header',
panelItem: 'igx-tab-content',
iconDirective: 'igxTabHeaderIcon',
labelDirective: 'igxTabHeaderLabel'
}
];
const EDITOR_COMPONENTS = [{
COMPONENT: 'igx-date-picker',
TEMPLATE_DIRECTIVE: 'igxDatePickerTemplate',
TEMPLATE_WARN_MSG: `\n<!-- igxDatePickerTemplate has been removed.
Label, prefix, suffix and hint can now be projected directly.
See https://www.infragistics.com/products/ignite-ui-angular/angular/components/date-picker -->\n`
}, {
COMPONENT: 'igx-time-picker',
TEMPLATE_DIRECTIVE: 'igxTimePickerTemplate',
TEMPLATE_WARN_MSG: `\n<!-- igxTimePickerTemplate has been removed.
Label, prefix, suffix and hint can now be projected directly.
See https://www.infragistics.com/products/ignite-ui-angular/angular/components/time-picker -->\n`
}];
const EDITORS_MODE = ['[mode]', 'mode'];
const EDITORS_LABEL = ['[label]', 'label'];
const EDITORS_LABEL_VISIBILITY = ['[labelVisibility]', 'labelVisibility'];
const update = new UpdateChanges_1.UpdateChanges(__dirname, host, context);
const changes = new Map();
const htmlFiles = update.templateFiles;
const sassFiles = update.sassFiles;
const _tsFiles = update.tsFiles;
let applyComment = false;
const applyChanges = () => {
for (const [path, change] of changes.entries()) {
let buffer = host.read(path).toString();
change.sort((c, c1) => c.position - c1.position)
.reverse()
.forEach(c => buffer = c.apply(buffer));
host.overwrite(path, buffer);
applyComment = true;
}
};
const addChange = (path, change) => {
if (changes.has(path)) {
changes.get(path).push(change);
}
else {
changes.set(path, [change]);
}
};
const isEmptyOrSpaces = (str) => str === null || str === '' || str === '\n' || str === '\r\n' || str.match(/^[\r\n\t]* *$/) !== null;
// Replace the tabsType input with tabsAligment
for (const path of htmlFiles) {
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), 'igx-tabs')
.forEach(node => {
if ((0, util_1.hasAttribute)(node, 'type')) {
const { startTag, file } = (0, util_1.getSourceOffset)(node);
const tabsType = (0, util_1.getAttribute)(node, 'type')[0];
let alignment;
if (tabsType.value.toLowerCase() === 'fixed') {
alignment = 'justify';
}
else if (tabsType.value.toLowerCase() === 'contentfit') {
alignment = 'start';
}
const tabAlignment = alignment ? ` tabAlignment="${alignment}"` : '';
addChange(file.url, new util_1.FileChange(startTag.end - 1, tabAlignment));
}
});
}
applyChanges();
changes.clear();
for (const comp of COMPONENTS) {
for (const path of htmlFiles) {
// Replace the <ng-template igxTab> if any with <igx-tab-item>
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.tags)
.map(tab => (0, util_1.findElementNodes)([tab], 'ng-template'))
.reduce((prev, curr) => prev.concat(curr), [])
.filter(template => (0, util_1.hasAttribute)(template, 'igxTab'))
.forEach(node => {
const { startTag, endTag, file } = (0, util_1.getSourceOffset)(node);
const content = file.content.substring(startTag.end, endTag.start);
const textToReplace = file.content.substring(startTag.start, endTag.end);
const tabPanel = `<${comp.headerItem}>${content}</${comp.headerItem}>`;
addChange(file.url, new util_1.FileChange(startTag.start, tabPanel, textToReplace, 'replace'));
});
applyChanges();
changes.clear();
// Convert label and icon to igx-tab-header children ->
// <igx-icon igxTabHeaderIcon> and <span igxTabHeaderLabel>
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.tags).
map(node => (0, util_1.getSourceOffset)(node)).
forEach(offset => {
const { startTag, file, node } = offset;
// Label content
let labelText = '';
if ((0, util_1.hasAttribute)(node, 'label')) {
const labelAttr = (0, util_1.getAttribute)(node, 'label')[0];
labelText = `\n<span ${comp.labelDirective}>${labelAttr.value}</span>`;
}
// Icon content
let iconText = '';
if ((0, util_1.hasAttribute)(node, 'icon')) {
const iconAttr = (0, util_1.getAttribute)(node, 'icon')[0];
iconText = `\n<igx-icon ${comp.iconDirective}>${iconAttr.value}</igx-icon>`;
}
// RouterLink
let routerLinkText = '';
if ((0, util_1.hasAttribute)(node, 'routerLink')) {
const routerLink = (0, util_1.getAttribute)(node, 'routerLink')[0];
routerLinkText = ` ${routerLink.name}="${routerLink.value}"`;
}
let classText = '';
if ((node.name === 'igx-tab-item' || node.name === 'igx-tab') && (0, util_1.hasAttribute)(node, 'class')) {
const classAttr = (0, util_1.getAttribute)(node, 'class')[0].value;
classText = !isEmptyOrSpaces(classAttr) ? ` class="${classAttr}"` : '';
}
if (iconText || labelText || routerLinkText) {
// Get igx-tab-header if it's present
const headerNode = (0, util_1.findElementNodes)([offset.node], comp.headerItem)
.map(hNode => (0, util_1.getSourceOffset)(hNode));
if (headerNode && headerNode.length > 0) {
addChange(file.url, new util_1.FileChange(headerNode[0].startTag.end - 1, `${routerLinkText}${classText}`));
}
else {
// eslint-disable-next-line max-len
const tabHeader = `\n<${comp.headerItem}${routerLinkText}${classText}>${iconText}${labelText}\n</${comp.headerItem}>`;
addChange(file.url, new util_1.FileChange(startTag.end, tabHeader));
}
}
});
applyChanges();
changes.clear();
// Grab the content between <igx-tabs-group> and create a <igx-tab-content>
// Also migrate class from igx-tabs-group to igx-tab-content, if any
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.tags)
.map(node => (0, util_1.getSourceOffset)(node))
.forEach(offset => {
const tabHeader = offset.node.children.find(c => c.name === comp.headerItem);
let classAttrText = '';
if (offset.node.name === 'igx-tab-panel' || offset.node.name === 'igx-tabs-group') {
const classAttr = (0, util_1.hasAttribute)(offset.node, 'class') ? (0, util_1.getAttribute)(offset.node, 'class')[0].value : '';
classAttrText = !isEmptyOrSpaces(classAttr) ? ` class="${classAttr}"` : '';
}
if (tabHeader) {
const content = offset.file.content.substring(tabHeader.sourceSpan.end.offset, offset.endTag.start);
// Since igx-tab-item tag is common for old and new igx-tabs
// Check whether igx-tab-content is already present!
const tabContentTag = new RegExp(String.raw `${comp.panelItem}`);
const hasTabContent = content.match(tabContentTag);
if (!hasTabContent && !isEmptyOrSpaces(content)) {
const tabPanel = `\n<${comp.panelItem}${classAttrText}>${content}</${comp.panelItem}>\n`;
addChange(offset.file.url, new util_1.FileChange(tabHeader.sourceSpan.end.offset, tabPanel, content, 'replace'));
}
}
});
applyChanges();
changes.clear();
// Insert a comment indicating the change/replacement
if (applyComment) {
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.component).
map(node => (0, util_1.getSourceOffset)(node)).
forEach(offset => {
const { startTag, file } = offset;
const commentText = UPDATE_NOTE;
addChange(file.url, new util_1.FileChange(startTag.start, commentText));
});
applyChanges();
changes.clear();
}
}
for (const sassPath of sassFiles) {
const origContent = host.read(sassPath).toString();
let newContent = origContent;
let changed = false;
for (const item of comp.tags) {
const searchText = new RegExp(String.raw `${item}(?=[\s])`);
let replaceText = '';
if (comp.component === 'igx-tabs') {
if (item === 'igx-tab-item') {
replaceText = comp.headerItem;
}
else if (item === 'igx-tabs-group') {
replaceText = comp.panelItem;
}
}
if (comp.component === 'igx-bottom-nav') {
if (item === 'igx-tab') {
replaceText = comp.headerItem;
}
else if (item === 'igx-tab-panel') {
replaceText = comp.panelItem;
}
}
if (searchText.test(origContent)) {
changed = true;
newContent = newContent.replace(searchText, replaceText);
}
}
if (changed) {
host.overwrite(sassPath, newContent);
}
}
}
// igxDatePicker & igxTimePicker migrations
for (const comp of EDITOR_COMPONENTS) {
for (const path of htmlFiles) {
// DatePicker and TimePicker don't support templates anymore.
// That is why migrations inserts a comment to notify the developer to remove the templates.
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.COMPONENT)
.map(editor => (0, util_1.findElementNodes)([editor], 'ng-template'))
.reduce((prev, curr) => prev.concat(curr), [])
.filter(template => (0, util_1.hasAttribute)(template, comp.TEMPLATE_DIRECTIVE))
.map(node => (0, util_1.getSourceOffset)(node))
.forEach(offset => {
const { startTag, file } = offset;
addChange(file.url, new util_1.FileChange(startTag.start, comp.TEMPLATE_WARN_MSG));
});
// DatePicker and TimePicker default mode is changed to dropdown.
// 1. That is why any occurrence of drop down mode is removed and
// 2. dialog mode is added for those that didn't explicitly set the mode prop.
// 1. Remove dropdown mode
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.COMPONENT)
.filter(template => (0, util_1.hasAttribute)(template, EDITORS_MODE))
.map(node => (0, util_1.getSourceOffset)(node))
.forEach(offset => {
const { file } = offset;
(0, util_1.getAttribute)(offset.node, EDITORS_MODE).forEach(attr => {
const { sourceSpan, value } = attr;
if (value.replace(/'/g, '').replace(/"/g, '') === 'dropdown') {
const attrKeyValue = file.content.substring(sourceSpan.start.offset, sourceSpan.end.offset);
addChange(file.url, new util_1.FileChange(sourceSpan.start.offset, '', attrKeyValue, 'replace'));
}
});
});
// 2. Insert dialog mode
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.COMPONENT)
.filter(template => !(0, util_1.hasAttribute)(template, EDITORS_MODE))
.map(node => (0, util_1.getSourceOffset)(node))
.forEach(offset => {
const { startTag, file } = offset;
addChange(file.url, new util_1.FileChange(startTag.end - 1, ' mode="dialog"'));
});
// Remove label property and project it as <label igxLabel></label>
// Check also labelVisibility value.
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.COMPONENT)
.filter(template => (0, util_1.hasAttribute)(template, EDITORS_LABEL))
.map(node => (0, util_1.getSourceOffset)(node))
.forEach(offset => {
const { startTag, file } = offset;
let visibilityValue = true;
if ((0, util_1.hasAttribute)(offset.node, EDITORS_LABEL_VISIBILITY)) {
const visibility = (0, util_1.getAttribute)(offset.node, EDITORS_LABEL_VISIBILITY);
visibilityValue = visibility[0].value;
}
(0, util_1.getAttribute)(offset.node, EDITORS_LABEL).forEach(attr => {
const { sourceSpan, name, value } = attr;
const attrKeyValue = file.content.substring(sourceSpan.start.offset, sourceSpan.end.offset);
let label;
const ngIF = (typeof visibilityValue === 'boolean') ? `` : ` *ngIf="${visibilityValue}"`;
if (name.startsWith('[')) {
label = `\n<label igxLabel${ngIF}>{{${value}}}</label>`;
}
else {
label = `\n<label igxLabel${ngIF}>${value}</label>`;
}
addChange(file.url, new util_1.FileChange(sourceSpan.start.offset, '', attrKeyValue, 'replace'));
addChange(file.url, new util_1.FileChange(startTag.end, label));
});
});
// If label and labelVisibility are not set this means that we should project default labels: "Date" & "Time"
(0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), comp.COMPONENT)
.filter(template => !(0, util_1.hasAttribute)(template, EDITORS_LABEL) &&
!(0, util_1.hasAttribute)(template, EDITORS_LABEL_VISIBILITY))
.map(node => (0, util_1.getSourceOffset)(node))
.forEach(offset => {
if ((0, util_1.findElementNodes)([offset.node], 'label').length === 0) {
const { startTag, file } = offset;
addChange(file.url, new util_1.FileChange(startTag.end, `\n<label igxLabel>${comp.COMPONENT === 'igx-date-picker' ? 'Date' : 'Time'}</label>`));
}
});
applyChanges();
changes.clear();
}
}
// input group disabled property
const INPUT_GROUP_CHANGES = {
GROUP_TAG: 'igx-input-group',
ATTRIBUTES: ['[disabled]', 'disabled'],
INPUT_TAG: ['input', 'textarea'],
DIRECTIVE: 'igxInput'
};
for (const path of htmlFiles) {
const inputGroups = (0, util_1.findElementNodes)((0, util_1.parseFile)(new HtmlParser(), host, path), INPUT_GROUP_CHANGES.GROUP_TAG);
inputGroups.forEach((el) => {
const parentEl = el;
if ((0, util_1.hasAttribute)(parentEl, INPUT_GROUP_CHANGES.ATTRIBUTES)) {
const inputChildren = (0, util_1.findElementNodes)([el], INPUT_GROUP_CHANGES.INPUT_TAG)
.reduce((prev, curr) => prev.concat(curr), [])
.filter(template => (0, util_1.hasAttribute)(template, INPUT_GROUP_CHANGES.DIRECTIVE));
INPUT_GROUP_CHANGES.ATTRIBUTES.forEach((a) => {
const attr = (0, util_1.getAttribute)(parentEl, a)[0];
if (attr) {
inputChildren.forEach((node) => {
if (!(0, util_1.hasAttribute)(node, INPUT_GROUP_CHANGES.ATTRIBUTES)) {
const { startTag, file } = (0, util_1.getSourceOffset)(node);
// input is self-closing, so the element === startTag
const repTxt = file.content.substring(startTag.start, startTag.end);
const matches = repTxt.match(/\/?>/g);
// should always be only 1 match
const lastIndex = repTxt.indexOf(matches[0]);
let property = `${attr.name}`;
if (attr.name === INPUT_GROUP_CHANGES.ATTRIBUTES[0] || attr.value) {
property += `="${attr.value}"`;
}
const addedAttr = `${repTxt.substring(0, lastIndex)} ${property}${repTxt.substring(lastIndex)}`;
addChange(file.url, new util_1.FileChange(startTag.start, addedAttr, repTxt, 'replace'));
}
});
}
});
}
});
applyChanges();
changes.clear();
}
// Apply all selector and input changes
update.shouldInvokeLS = options['shouldInvokeLS'];
update.applyChanges();
});
;