Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c318950
feat: get object page section label
AlinaGovoruhina Apr 17, 2026
3ae0b4b
feat: use section name in table rules
AlinaGovoruhina Apr 17, 2026
5559f39
fix: typo in test data
AlinaGovoruhina Apr 17, 2026
77b88d2
test: check reported message
AlinaGovoruhina Apr 17, 2026
84558d0
Linting auto fix commit
github-actions[bot] Apr 17, 2026
d0b2e22
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina Apr 21, 2026
c471ac4
refactor: fix code issues
AlinaGovoruhina Apr 21, 2026
96e5336
chore: add changeset
AlinaGovoruhina Apr 22, 2026
aef7803
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina Apr 24, 2026
cb8914a
chore: add changeset
AlinaGovoruhina Apr 24, 2026
b84eb9e
chore: remove duplicate changeset
AlinaGovoruhina Apr 24, 2026
b8ed6c9
test: creation mode messages
AlinaGovoruhina Apr 24, 2026
de46233
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina Apr 24, 2026
7141050
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina Apr 27, 2026
9c9ff0c
fix: typos
AlinaGovoruhina Apr 27, 2026
ba94e65
test: personalization messages
AlinaGovoruhina Apr 27, 2026
0d7a228
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina Apr 28, 2026
a243e6e
refactor: check table type
AlinaGovoruhina Apr 28, 2026
030c8b3
refactor: type
AlinaGovoruhina Apr 28, 2026
c54e55a
refactor: reduce duplication
AlinaGovoruhina Apr 28, 2026
2c929cc
chore: add jsdoc
AlinaGovoruhina Apr 29, 2026
6c17091
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
marufrasully Apr 29, 2026
551a078
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina May 11, 2026
3f3b32d
refactor: define args type
AlinaGovoruhina May 13, 2026
19779bd
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina May 13, 2026
ea540e1
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina May 14, 2026
e5039aa
Merge branch 'main' into feat/eslint-fiori/report-page-section-name
AlinaGovoruhina May 15, 2026
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
5 changes: 5 additions & 0 deletions .changeset/busy-clouds-sink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sap-ux/eslint-plugin-fiori-tools': patch
---

Update table configuration related eslint rules to include object page section label in the reported issue message.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface WidthIncludingColumnHeaderDiagnostic {
type: typeof WIDTH_INCLUDING_COLUMN_HEADER_RULE_TYPE;
manifest: ManifestPropertyDiagnosticData;
pageName: string;
pageSectionName?: string;
Comment thread
marufrasully marked this conversation as resolved.
annotation: {
file: string;
annotationPath: string;
Expand Down Expand Up @@ -54,6 +55,7 @@ export type CreateModeMessageId =
export interface CreationModeForTable {
type: typeof CREATION_MODE_FOR_TABLE;
pageName: string;
pageSectionName?: string;
manifest: ManifestPropertyDiagnosticData;
messageId: CreateModeMessageId;
tableType: string;
Expand All @@ -64,18 +66,21 @@ export interface CreationModeForTable {
export interface CopyToClipboard {
type: typeof COPY_TO_CLIPBOARD;
pageName: string;
pageSectionName?: string;
manifest: ManifestPropertyDiagnosticData;
}

export interface EnableExport {
type: typeof ENABLE_EXPORT;
pageName: string;
pageSectionName?: string;
manifest: ManifestPropertyDiagnosticData;
}

export interface EnablePaste {
type: typeof ENABLE_PASTE;
pageName: string;
pageSectionName?: string;
manifest: ManifestPropertyDiagnosticData;
}

Expand Down Expand Up @@ -107,6 +112,7 @@ export interface TablePersonalization {
property?: PersonalizationProperty;
undefinedProperties?: PersonalizationProperty[];
pageName: string;
pageSectionName?: string;
manifest: ManifestPropertyDiagnosticData;
}

Expand All @@ -130,6 +136,7 @@ export interface NoDataFieldIntentBasedNavigation {
export interface CondensedTableLayout {
type: typeof CONDENSED_TABLE_LAYOUT;
pageName: string;
pageSectionName?: string;
manifest: ManifestPropertyDiagnosticData;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ import type { IndexedAnnotation, ParsedService } from '../parser';
import { buildAnnotationIndexKey } from '../parser';
import { UI_FIELD_GROUP, UI_LINE_ITEM } from '../../constants';

/**
* index - Index of annotation
* referencedEntityType - Entity type
* qualifier - FieldGroup annotation qualifier
* sectionLabel - Object page section label
*/
type SectionConfig = {
index: number;
referencedEntityType: string;
qualifier?: string;
sectionLabel?: string;
};

/**
* Creates a configuration key from an annotation path
*
Expand All @@ -28,6 +41,7 @@ export function getConfigurationKey(annotationPath: string): string {
export interface AnnotationBasedNode<T extends string, Children = never> {
type: T;
annotation: IndexedAnnotation;
label?: string;
/**
* Path used by Fiori elements to reference this control
*/
Expand Down Expand Up @@ -186,12 +200,28 @@ function processReferenceFacetRecord(
const [, _annotationPath] = fullyQualifiedPath.split('@');
const [term, qualifier] = _annotationPath.split('#');

const propValues = elementsWithName(Edm.PropertyValue, record);
const propValue = propValues.find((p) => p.attributes.Property?.value === 'Label');
const sectionLabel = propValue?.attributes?.String?.value;

if (term === UI_LINE_ITEM) {
return createTableSection(facets, index, referencedEntityType, qualifier, annotationPath, aliasInfo, service);
return createTableSection(
facets,
{ index, referencedEntityType, qualifier, sectionLabel },
annotationPath,
aliasInfo,
service
);
}

if (term === UI_FIELD_GROUP) {
return addHeaderSection(facets, index, referencedEntityType, qualifier, annotationPath, aliasInfo, service);
return addHeaderSection(
facets,
{ index, referencedEntityType, qualifier, sectionLabel },
annotationPath,
aliasInfo,
service
);
}

return undefined;
Expand Down Expand Up @@ -228,36 +258,34 @@ function getReferencedEntityType(
* Create a table section node with its child table node.
*
* @param facets
* @param index
* @param referencedEntityType
* @param qualifier
* @param config
* @param annotationPath
* @param aliasInfo
* @param service
* @returns
*/
function createTableSection(
facets: IndexedAnnotation,
index: number,
referencedEntityType: string,
qualifier: string | undefined,
config: SectionConfig,
annotationPath: string,
aliasInfo: AliasInformation,
service: ParsedService
): TableSectionNode | undefined {
Comment thread
AlinaGovoruhina marked this conversation as resolved.
const section: TableSectionNode = {
type: 'table-section',
annotationPath: `@com.sap.vocabularies.UI.v1.Facets/${index}`,
annotationPath: `@com.sap.vocabularies.UI.v1.Facets/${config.index}`,
label: config.sectionLabel,
annotation: facets,
children: []
};

const lineItemKey = buildAnnotationIndexKey(referencedEntityType, UI_LINE_ITEM);
const lineItemKey = buildAnnotationIndexKey(config.referencedEntityType, UI_LINE_ITEM);
const tableAnnotations = service.index.annotations[lineItemKey];
if (!tableAnnotations) {
return undefined;
}

const annotation = tableAnnotations[qualifier ?? 'undefined'];
const annotation = tableAnnotations[config.qualifier ?? 'undefined'];
if (!annotation) {
return undefined;
}
Expand All @@ -280,37 +308,34 @@ function createTableSection(
* Creates a header facet section node with field group child annotation.
*
* @param headerFacets - Header facet annotation
* @param index - Index of annotation
* @param referencedEntityType - Entity type
* @param qualifier - FieldGroup annotation qualifier
* @param config - Section configuration
* @param annotationPath - Header facet annotation path
* @param aliasInfo - Alias information for resolving namespaces
* @param service - The parsed OData service
* @returns Header section annotation node
*/
function addHeaderSection(
headerFacets: IndexedAnnotation,
index: number,
referencedEntityType: string,
qualifier: string | undefined,
config: SectionConfig,
annotationPath: string,
aliasInfo: AliasInformation,
service: ParsedService
): HeaderSectionNode | undefined {
const section: HeaderSectionNode = {
type: 'header-section',
annotationPath: `@com.sap.vocabularies.UI.v1.HeaderFacet/${index}`,
annotationPath: `@com.sap.vocabularies.UI.v1.HeaderFacet/${config.index}`,
annotation: headerFacets,
label: config.sectionLabel,
children: []
};

const fieldGroupKey = buildAnnotationIndexKey(referencedEntityType, UI_FIELD_GROUP);
const fieldGroupKey = buildAnnotationIndexKey(config.referencedEntityType, UI_FIELD_GROUP);
const fieldGroupAnnotations = service.index.annotations[fieldGroupKey];
if (!fieldGroupAnnotations) {
return undefined;
}

const annotation = fieldGroupAnnotations[qualifier ?? 'undefined'];
const annotation = fieldGroupAnnotations[config.qualifier ?? 'undefined'];
if (!annotation) {
return undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface AnnotationBasedNode<
Children = never
> extends ConfigurationBase<T['type'], Configuration> {
annotation?: T;

label?: string;
children: Children[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface AnnotationBasedNode<
Children = never
> extends ConfigurationBase<T['type'], Configuration> {
annotation?: T;
label?: string;
children: Children[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import type { FioriRuleDefinition } from '../types';
import { COPY_TO_CLIPBOARD, type CopyToClipboard } from '../language/diagnostics';
import { createFioriRule } from '../language/rule-factory';
import type { MemberNode } from '@humanwhocodes/momoa';
import type { FeV2PageType } from '../project-context/linker/fe-v2';
import type { FeV2PageType, Table as TableV2 } from '../project-context/linker/fe-v2';
import type { ParsedApp } from '../project-context/parser';
import type { FeV4PageType } from '../project-context/linker/fe-v4';
import type { FeV4PageType, Table as TableV4 } from '../project-context/linker/fe-v4';
import { createJsonFixer } from '../language/rule-fixer';
import { checkAppTablesConfiguration } from '../utils/helpers';

const rule: FioriRuleDefinition = createFioriRule({
ruleId: COPY_TO_CLIPBOARD,
Expand All @@ -18,7 +19,7 @@ const rule: FioriRuleDefinition = createFioriRule({
},
messages: {
[COPY_TO_CLIPBOARD]:
'Copy To Clipboard must be correctly configured. If not set, the "Copy" button is displayed'
'Copy To Clipboard in the {{sectionText}}table must be correctly configured. If not set, the "Copy" button is displayed'
},
fixable: 'code'
},
Expand All @@ -32,14 +33,8 @@ const rule: FioriRuleDefinition = createFioriRule({
if (!parsedService) {
continue;
}
if (app.type === 'fe-v2') {
for (const page of app.pages) {
problems.push(...handleCopyInTableV2(page, parsedApp));
}
} else if (app.type === 'fe-v4') {
for (const page of app.pages) {
problems.push(...handleDisableCopyInTableV4(page, parsedApp));
}
for (const page of app.pages) {
problems.push(...(<CopyToClipboard[]>checkAppTablesConfiguration(page, parsedApp, checkConfiguration)));
}
}
return problems;
Expand All @@ -49,6 +44,9 @@ const rule: FioriRuleDefinition = createFioriRule({
context.report({
node,
messageId: COPY_TO_CLIPBOARD,
data: {
sectionText: diagnostic.pageSectionName ? `${diagnostic.pageSectionName} ` : ''
Comment thread
AlinaGovoruhina marked this conversation as resolved.
},
fix: createJsonFixer({
context,
deepestPathResult,
Expand All @@ -60,53 +58,50 @@ const rule: FioriRuleDefinition = createFioriRule({
});

/**
* Looks through V2 app page tables and returns problems if copy is set to false.
*
* @param page - V2 app page
* @param parsedApp - parsed V2 app
* @returns - CopyToClipoard issues
* @param table
* @returns
*/
function handleCopyInTableV2(page: FeV2PageType, parsedApp: ParsedApp): CopyToClipboard[] {
const problems: CopyToClipboard[] = [];
for (const table of page.lookup['table'] ?? []) {
if (table.configuration.copy.valueInFile === false) {
problems.push({
type: COPY_TO_CLIPBOARD,
pageName: page.targetName,
manifest: {
uri: parsedApp.manifest.manifestUri,
object: parsedApp.manifestObject,
propertyPath: table.configuration.copy.configurationPath
}
});
}
}
return problems;
function isV2Table(table: TableV2 | TableV4): table is TableV2 {
return 'copy' in (table as TableV2).configuration;
}

/**
* Looks through V4 app page tables and returns problems if disableCopyToClipboard is set to true.
*
* @param page - V4 app page
* @param parsedApp - parsed V4 app
* @returns - CopyToClipoard issues
* @param page
* @param table
* @param parsedApp
* @param problems
* @param pageSectionName
*/
function handleDisableCopyInTableV4(page: FeV4PageType, parsedApp: ParsedApp): CopyToClipboard[] {
const problems: CopyToClipboard[] = [];
for (const table of page.lookup['table'] ?? []) {
if (table.configuration.disableCopyToClipboard.valueInFile === true) {
problems.push({
type: COPY_TO_CLIPBOARD,
pageName: page.targetName,
manifest: {
uri: parsedApp.manifest.manifestUri,
object: parsedApp.manifestObject,
propertyPath: table.configuration.disableCopyToClipboard.configurationPath
}
});
}
function checkConfiguration(
page: FeV4PageType | FeV2PageType,
table: TableV4 | TableV2,
parsedApp: ParsedApp,
problems: CopyToClipboard[],
pageSectionName?: string
): void {
let config;
let wrongValue = false;
if (isV2Table(table)) {
config = table.configuration.copy;
} else {
config = table.configuration.disableCopyToClipboard;
wrongValue = true;
}
if (config?.valueInFile === wrongValue) {
const copyIssue: CopyToClipboard | undefined = {
type: COPY_TO_CLIPBOARD,
pageName: page.targetName,
pageSectionName,
manifest: {
uri: parsedApp.manifest.manifestUri,
object: parsedApp.manifestObject,
propertyPath: config.configurationPath
}
};
problems.push(copyIssue);
}
return problems;
}

export default rule;
Loading
Loading