Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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 packages/eslint-plugin-fiori-tools/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ export const configs: Record<string, Linter.Config[]> = {
'@sap-ux/fiori-tools/sap-enable-paste': 'warn',
'@sap-ux/fiori-tools/sap-creation-mode-for-table': 'warn',
'@sap-ux/fiori-tools/sap-state-preservation-mode': 'warn',
'@sap-ux/fiori-tools/sap-table-personalization': 'warn',
'@sap-ux/fiori-tools/sap-table-column-vertical-alignment': 'warn'
}
}
Expand Down
19 changes: 19 additions & 0 deletions packages/eslint-plugin-fiori-tools/src/language/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { Manifest } from '@sap-ux/project-access';
import type { AnnotationReference } from '../project-context/parser';
import type { SourceLocation } from '@eslint/core';
export const WIDTH_INCLUDING_COLUMN_HEADER_RULE_TYPE = 'sap-width-including-column-header';
export const FLEX_ENABLED = 'sap-flex-enabled';
export const COPY_TO_CLIPBOARD = 'sap-copy-to-clipboard';
export const ENABLE_EXPORT = 'sap-enable-export';
export const ENABLE_PASTE = 'sap-enable-paste';
export const CREATION_MODE_FOR_TABLE = 'sap-creation-mode-for-table';
export const STATE_PRESERVATION_MODE = 'sap-state-preservation-mode';
export const TABLE_PERSONALIZATION = 'sap-table-personalization';
export const TABLE_COLUMN_VERTICAL_ALIGNMENT = 'sap-table-column-vertical-alignment';

export interface WidthIncludingColumnHeaderDiagnostic {
Expand All @@ -24,6 +26,7 @@ export interface ManifestPropertyDiagnosticData {
uri: string;
object: Manifest;
propertyPath: string[];
loc?: SourceLocation;
}

export interface FlexEnabled {
Expand Down Expand Up @@ -79,6 +82,21 @@ export interface StatePreservationMode {
value?: string;
}

export type PersonalizationProperty = 'column' | 'filter' | 'sort' | 'group';
export type PersonalizationMessageId =
| 'sap-table-personalization'
| 'sap-table-personalization-column'
| 'sap-table-personalization-filter'
| 'sap-table-personalization-sort'
| 'sap-table-personalization-group';
export interface TablePersonalization {
type: typeof TABLE_PERSONALIZATION;
messageId: PersonalizationMessageId;
property?: PersonalizationProperty;
pageName: string;
manifest: ManifestPropertyDiagnosticData;
}

export interface TableColumnVerticalAlignment {
type: typeof TABLE_COLUMN_VERTICAL_ALIGNMENT;
manifest: ManifestPropertyDiagnosticData;
Expand All @@ -92,4 +110,5 @@ export type Diagnostic =
| EnableExport
| EnablePaste
| StatePreservationMode
| TablePersonalization
| TableColumnVerticalAlignment;
26 changes: 22 additions & 4 deletions packages/eslint-plugin-fiori-tools/src/language/rule-fixer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { JSONRuleContext } from './rule-factory';
import type { AnyNode, MemberNode, ObjectNode } from '@humanwhocodes/momoa';
import type { DeepestExistingPathResult } from '../utils/helpers';
import { type DeepestExistingPathResult } from '../utils/helpers';
import type { RuleTextEdit, RuleTextEditor } from '@eslint/core';

/**
Expand Down Expand Up @@ -38,6 +38,13 @@ export interface JsonFixerConfig<MessageIds extends string, RuleOptions extends
* If not specified, it will be inferred based on the context.
*/
operation?: 'insert' | 'update' | 'delete';

/**
* Whether the operation has to be performed on the parent node.
* Example: if personalization.filter is disabled, set personalization to true,
* instead of updating the value of filter property only.
*/
fixParent?: boolean;
}

/**
Expand Down Expand Up @@ -104,21 +111,32 @@ export function createJsonFixer<MessageIds extends string, RuleOptions extends u
}
}

const fixParent = config.fixParent;
let fixNode = node;
if (fixParent) {
// Parent object node (contains member nodes)
const parentObjectNode: AnyNode | undefined = context.sourceCode.getParent(node);
// Parent member node (contains value)
const parentMemberNode: AnyNode | undefined =
parentObjectNode && context.sourceCode.getParent(parentObjectNode);
fixNode = parentMemberNode?.type === 'Member' ? parentMemberNode : node;
}

return (fixer: RuleTextEditor) => {
try {
switch (fixOperation) {
case 'update': {
const result = handleUpdate(fixer, node, value);
const result = handleUpdate(fixer, fixNode, value);
return result ? [result] : [];
}

case 'insert': {
const result = handleInsert(fixer, node, missingSegments, value);
const result = handleInsert(fixer, fixNode, missingSegments, value);
return result ? [result] : [];
}

case 'delete': {
const result = handleDelete(fixer, node, context);
const result = handleDelete(fixer, fixNode, context);
if (!result) {
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface TableSettings {
disableCopyToClipboard: boolean;
enableExport: boolean;
enablePaste: boolean;
personalization: boolean | { column?: boolean; filter?: boolean; sort?: boolean; group?: boolean };
}

export type OrphanTable = ConfigurationBase<'orphan-table', TableSettings>;
Expand Down Expand Up @@ -149,6 +150,18 @@ function createTable(configurationKey: string, pathToPage: string[], table?: Tab
'name'
],
values: getCreationModeValues()
},
personalization: {
configurationPath: [
...pathToPage,
'options',
'settings',
'controlConfiguration',
configurationKey,
'tableSettings',
'personalization'
],
values: [true, false, {}]
}
}
};
Expand Down Expand Up @@ -257,6 +270,14 @@ interface TableConfiguration {
creationMode?: {
name?: string;
};
personalization?:
| boolean
| {
column?: boolean;
filter?: boolean;
sort?: boolean;
group?: boolean;
};
};
}

Expand Down Expand Up @@ -346,6 +367,8 @@ function linkListReportTable(
const creationModeValue = controlConfiguration.tableSettings?.creationMode?.name;
tableControl.configuration.creationMode.valueInFile = creationModeValue;
tableControl.configuration.creationMode.values = getCreationModeValues(tableType);
const personalization = controlConfiguration.tableSettings?.personalization;
tableControl.configuration.personalization.valueInFile = personalization;
}
} else {
// no annotation definition found for this table, but configuration exists
Expand Down Expand Up @@ -439,6 +462,8 @@ function linkObjectPageSections(
const creationModeValue = controlConfiguration.tableSettings?.creationMode?.name;
tableControl.configuration.creationMode.valueInFile = creationModeValue;
tableControl.configuration.creationMode.values = getCreationModeValues(tableType);
const personalization = controlConfiguration.tableSettings?.personalization;
tableControl.configuration.personalization.valueInFile = personalization;
} else {
// no annotation definition found for this section, but configuration exists
const orphanedSection: OrphanSection = {
Expand Down
3 changes: 3 additions & 0 deletions packages/eslint-plugin-fiori-tools/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ENABLE_EXPORT,
ENABLE_PASTE,
STATE_PRESERVATION_MODE,
TABLE_PERSONALIZATION,
TABLE_COLUMN_VERTICAL_ALIGNMENT
} from '../language/diagnostics';

Expand Down Expand Up @@ -68,6 +69,7 @@ import statePreservationMode from './sap-state-preservation-mode';
import copyToClipboard from './sap-copy-to-clipboard';
import enableExport from './sap-enable-export';
import enablePaste from './sap-enable-paste';
import tablePersonalization from './sap-table-personalization';
import tableColumnVerticalAlignment from './sap-table-column-vertical-alignment';

import type { Rule } from 'eslint';
Expand Down Expand Up @@ -128,5 +130,6 @@ export const rules: Record<string, Rule.RuleModule | FioriRuleDefinition | Fiori
[ENABLE_PASTE]: enablePaste,
[CREATION_MODE_FOR_TABLE]: creationModeForTable,
[STATE_PRESERVATION_MODE]: statePreservationMode,
[TABLE_PERSONALIZATION]: tablePersonalization,
[TABLE_COLUMN_VERTICAL_ALIGNMENT]: tableColumnVerticalAlignment
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import type { FioriRuleDefinition } from '../types';
import {
type PersonalizationMessageId,
type PersonalizationProperty,
TABLE_PERSONALIZATION,
type TablePersonalization
} from '../language/diagnostics';
import { createFioriRule } from '../language/rule-factory';
import type { MemberNode } from '@humanwhocodes/momoa';
import { createJsonFixer } from '../language/rule-fixer';
import type { FeV4PageType, Table } from '../project-context/linker/fe-v4';
import type { ParsedApp } from '../project-context/parser';
import { isLowerThanMinimalUi5Version } from '../utils/version';

const PersonalizationProperties = ['column', 'filter', 'group', 'sort'];

const TABLE_PERSONALIZATION_COLUMN = 'sap-table-personalization-column';
const TABLE_PERSONALIZATION_FILTER = 'sap-table-personalization-filter';
const TABLE_PERSONALIZATION_SORT = 'sap-table-personalization-sort';
const TABLE_PERSONALIZATION_GROUP = 'sap-table-personalization-group';

const MessageIdByProperty: {
[key: string]: PersonalizationMessageId;
} = {
['']: TABLE_PERSONALIZATION,
column: TABLE_PERSONALIZATION_COLUMN,
filter: TABLE_PERSONALIZATION_FILTER,
sort: TABLE_PERSONALIZATION_SORT,
group: TABLE_PERSONALIZATION_GROUP
};

const rule: FioriRuleDefinition = createFioriRule({
ruleId: TABLE_PERSONALIZATION,
meta: {
type: 'suggestion',
docs: {
recommended: true,
description:
'You can use table personalization to modify the settings of a table. By default, the table personalization provides options for adding or removing columns, filtering, sorting, and grouping.',
url: 'https://github.com/SAP/open-ux-tools/blob/main/packages/eslint-plugin-fiori-tools/docs/rules/sap-table-personalization.md'
},
messages: {
[TABLE_PERSONALIZATION]:
'Table personalization should be enabled. Currently every table personalization setting is disabled.',
[TABLE_PERSONALIZATION_COLUMN]: 'Adding or removing table columns should be enabled.',
[TABLE_PERSONALIZATION_FILTER]: 'Table data filtering should be enabled.',
[TABLE_PERSONALIZATION_SORT]: 'Table data sorting should be enabled.',
[TABLE_PERSONALIZATION_GROUP]:
'Table data grouping should be enabled for analytical and responsive type tables.'
},
fixable: 'code'
},

check(context) {
const problems: TablePersonalization[] = [];

for (const [appKey, app] of Object.entries(context.sourceCode.projectContext.linkedModel.apps)) {
if (app.type !== 'fe-v4') {
continue;
}
const parsedApp = context.sourceCode.projectContext.index.apps[appKey];
const parsedService = context.sourceCode.projectContext.getIndexedServiceForMainService(parsedApp);
if (!parsedService) {
continue;
}

for (const page of app.pages) {
for (const table of page.lookup['table'] ?? []) {
const tableProblems = checkPersonalizationValue(table, page, parsedApp);
problems.push(...tableProblems);
}
}
}
return problems;
},
createJsonVisitorHandler: (context, diagnostic, deepestPathResult) => {
return function report(node: MemberNode): void {
diagnostic.manifest.loc = node.loc;
return context.report({
node,
messageId: MessageIdByProperty[diagnostic.property ?? ''],
fix: createJsonFixer({
context,
deepestPathResult,
node,
operation: 'update',
value: true,
fixParent: !!diagnostic.property
})
});
};
}
});

/**
*
* @param table
* @param parsedApp
* @param pageName
* @returns
*/
function checkGroupProperty(table: Table, parsedApp: ParsedApp, pageName: string): TablePersonalization[] {
const minUI5Version = parsedApp.manifest.minUI5Version;
const tableType = table.configuration.tableType.valueInFile;
const checkGroupForAnalyticalTable =
tableType === 'AnalyticalTable' &&
minUI5Version &&
!isLowerThanMinimalUi5Version(minUI5Version, { major: 1, minor: 108 });
const checkGroupForResponsiveTable =
tableType === 'ResponsiveTable' &&
minUI5Version &&
!isLowerThanMinimalUi5Version(minUI5Version, { major: 1, minor: 120 });
if (checkGroupForAnalyticalTable || checkGroupForResponsiveTable) {
return [
{
type: TABLE_PERSONALIZATION,
pageName,
property: 'group',
messageId: MessageIdByProperty['group'],
manifest: {
uri: parsedApp.manifest.manifestUri,
object: parsedApp.manifestObject,
propertyPath: [...table.configuration.personalization.configurationPath, 'group']
}
}
];
}
return [];
}

/**
*
* @param table
* @param page
* @param parsedApp
* @returns
*/
function checkPersonalizationValue(table: Table, page: FeV4PageType, parsedApp: ParsedApp): TablePersonalization[] {
const problems: TablePersonalization[] = [];

const personalization = table.configuration.personalization.valueInFile;
// Check if boolean value
if (personalization === true || personalization === undefined) {
// Every table personalization setting is enabled
return [];
}
if (personalization === false || Object.keys(personalization).length === 0) {
// Every table personalization setting is disabled
problems.push({
type: TABLE_PERSONALIZATION,
pageName: page.targetName,
messageId: MessageIdByProperty[''],
manifest: {
uri: parsedApp.manifest.manifestUri,
object: parsedApp.manifestObject,
propertyPath: table.configuration.personalization.configurationPath
}
});
} else {
// Check personalization object properties
for (const key of PersonalizationProperties) {
const property = key as PersonalizationProperty;
if (personalization[property] === false) {
if (property === 'group') {
problems.push(...checkGroupProperty(table, parsedApp, page.targetName));
} else {
problems.push({
type: TABLE_PERSONALIZATION,
pageName: page.targetName,
property,
messageId: MessageIdByProperty[property],
manifest: {
uri: parsedApp.manifest.manifestUri,
object: parsedApp.manifestObject,
propertyPath: [...table.configuration.personalization.configurationPath, property]
}
});
}
}
}
}
return problems;
}

export default rule;
Loading