Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/demos/testing/known-warnings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export const knownWarnings = [
'W0019 -',
'W0022 -',
'W2108 -',
'W3001 -'
];
1,090 changes: 1,090 additions & 0 deletions packages/devextreme-angular/src/core/deprecated-config-map.ts

Large diffs are not rendered by default.

81 changes: 81 additions & 0 deletions packages/devextreme-angular/src/core/deprecated-config-warning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { BaseNestedOption, INestedOptionContainer } from './nested-option';
import { DEPRECATED_CONFIG_COMPONENTS } from './deprecated-config-map';
import WARNING_CODES from './warning-codes';
import { logWarning } from './warning-helper';

const warnedUsages = new Set<string>();

const LEGACY_CLASS_NAME_REGEXP = /^Dx([io])([A-Za-z0-9]+)Component$/;

type DeprecatedConfigEntry = Record<string, string>;

function getLegacySelector(nestedOption: BaseNestedOption): string | undefined {
const className = nestedOption?.constructor?.name;
if (!className) {
return undefined;
}

const match = LEGACY_CLASS_NAME_REGEXP.exec(className);
if (!match) {
return undefined;
}

const [, type, rest] = match;
const prefix = type === 'o' ? 'dxo-' : 'dxi-';

return `${prefix}${toKebabCase(rest)}`;
}

function getHostMapping(host: INestedOptionContainer | undefined): DeprecatedConfigEntry | undefined {
const visited = new Set<INestedOptionContainer>();
let current = host;

while (current && !visited.has(current)) {
visited.add(current);

const ctorName = current.constructor?.name;
if (ctorName && Object.prototype.hasOwnProperty.call(DEPRECATED_CONFIG_COMPONENTS, ctorName)) {
return DEPRECATED_CONFIG_COMPONENTS[ctorName] as DeprecatedConfigEntry;
}

current = (current as { _host?: INestedOptionContainer })._host;
}

return undefined;
}

export function warnIfLegacyNestedComponent(nestedOption: BaseNestedOption, host: INestedOptionContainer | undefined): void {
const legacySelector = getLegacySelector(nestedOption);
if (!legacySelector) {
return;
}

const mappingEntry = getHostMapping(host);
if (!mappingEntry) {
return;
}

const replacement = mappingEntry[legacySelector];
if (!replacement) {
return;
}

const cacheKey = `${legacySelector}|${replacement}`;
if (warnedUsages.has(cacheKey)) {
return;
}

warnedUsages.add(cacheKey);

logWarning(
WARNING_CODES.LEGACY_CONFIG_COMPONENT_USED,
{ legacySelector, replacement },
);
}

function toKebabCase(value: string): string {
return value
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
.toLowerCase();
}
2 changes: 2 additions & 0 deletions packages/devextreme-angular/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export * from './template';
export * from './transfer-state';
export * from './utils';
export * from './watcher-helper';
export * from './warning-helper';
export * from './warning-codes';
2 changes: 2 additions & 0 deletions packages/devextreme-angular/src/core/nested-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import render from 'devextreme/core/renderer';
import { triggerHandler } from 'devextreme/events';
import domAdapter from 'devextreme/core/dom_adapter';
import { getElement } from './utils';
import { warnIfLegacyNestedComponent } from './deprecated-config-warning';
import { DX_TEMPLATE_WRAPPER_CLASS } from './template';

const VISIBILITY_CHANGE_SELECTOR = 'dx-visibility-change-handler';
Expand Down Expand Up @@ -292,5 +293,6 @@ export class NestedOptionHost {

setNestedOption(nestedOption: BaseNestedOption) {
nestedOption.setHost(this._host, this._optionPath);
warnIfLegacyNestedComponent(nestedOption, this._host);
}
}
13 changes: 13 additions & 0 deletions packages/devextreme-angular/src/core/warning-codes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const WARNING_CODES = {
LEGACY_CONFIG_COMPONENT_USED: {
code: 'W3001',
template: 'You are using the legacy {legacySelector} configuration component. We recommend '
+ 'upgrading to the new {replacement} configuration component.',
// TODO: link to docs article, when available
},
} as const;

export type WarningId = keyof typeof WARNING_CODES;
export type WarningDefinition = typeof WARNING_CODES[WarningId];

export default WARNING_CODES;
35 changes: 35 additions & 0 deletions packages/devextreme-angular/src/core/warning-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { WarningDefinition } from './warning-codes';

type TemplatePrimitive = string | number | boolean;
export type TemplateArgs = TemplatePrimitive[] | Record<string, TemplatePrimitive>;

function formatWarningMessage(template: string, args?: TemplateArgs): string {
if (!args) {
return template;
}

if (Array.isArray(args)) {
return args.reduce<string>(
(message, value, index) => replacePlaceholder(message, `{${index}}`, String(value)),
template,
);
}

return (Object.entries(args) as Array<[string, TemplatePrimitive]>).reduce<string>(
(message, [key, value]) => replacePlaceholder(message, `{${key}}`, String(value)),
template,
);
}

export function logWarning(warning: WarningDefinition, args?: TemplateArgs): void {
if (typeof console === 'undefined' || typeof console.warn !== 'function') {
return;
}

const message = formatWarningMessage(warning.template, args);
console.warn(`${warning.code} - ${message}`);
}

function replacePlaceholder(message: string, placeholder: string, value: string): string {
return message.split(placeholder).join(value);
}
Loading