Skip to content
Merged
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
102 changes: 102 additions & 0 deletions .codescene/code-health-rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,108 @@
"value": 6
}
]
},
{
"matching_content_path": "packages/adt-plugin-abapgit/src/schemas/generated/**",
"matching_content_path_doc": "XSD codegen output (schema literals + interfaces) regenerated by `nx codegen adt-plugin-abapgit`; not hand-maintained. Fixes gap: existing **/*.generated.ts glob does not match files inside generated/ directories.",
"rules": [
{
"name": "Brain Method",
"weight": 0.0
},
{
"name": "Bumpy Road Ahead",
"weight": 0.0
},
{
"name": "Code Duplication",
"weight": 0.0
},
{
"name": "Complex Conditional",
"weight": 0.0
},
{
"name": "Complex Method",
"weight": 0.0
},
{
"name": "Deep, Nested Complexity",
"weight": 0.0
},
{
"name": "Excess Number of Function Arguments",
"weight": 0.0
},
{
"name": "Large Method",
"weight": 0.0
},
{
"name": "Lines of Code in a Single File",
"weight": 0.0
},
{
"name": "Number of Functions in a Single Module",
"weight": 0.0
},
{
"name": "Overall Code Complexity",
"weight": 0.0
},
{
"name": "Overall Function Size",
"weight": 0.0
},
{
"name": "Primitive Obsession",
"weight": 0.0
},
{
"name": "String Heavy Function Arguments",
"weight": 0.0
}
]
},
{
"matching_content_path": "packages/adt-plugin-abapgit/src/lib/handlers/objects/*.ts",
"matching_content_path_doc": "abapGit object handlers are declarative XSD-schema field mappers; long ?? / ?. fallback chains and SAP field-name string literals are inherent to the mapping layer, not fixable complexity. Real control-flow smells (Deep Nested, Bumpy Road, Large Method) stay enabled.",
"rules": [
{
"name": "Complex Method",
"weight": 0.0
},
{
"name": "Complex Conditional",
"weight": 0.5
},
{
"name": "Primitive Obsession",
"weight": 0.0
},
{
"name": "String Heavy Function Arguments",
"weight": 0.0
},
{
"name": "Code Duplication",
"weight": 0.5
}
],
"thresholds": [
{
"name": "function_cyclomatic_complexity_warning",
"value": 25
},
{
"name": "function_lines_of_code_warning",
"value": 160
},
{
"name": "file_mean_cyclomatic_complexity_warning",
"value": 8
}
]
}
]
}
31 changes: 31 additions & 0 deletions packages/adt-plugin-abapgit/src/lib/handlers/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,37 @@ export interface HandlerContext<T extends AdkObject, _TData = unknown> {
): SerializedFile;
}

/**
* Normalize an XML child element that may be a single item or array into an array
*/
export function normalizeItems<T>(raw: T | T[] | undefined): T[] {
if (!raw) return [];
return Array.isArray(raw) ? raw : [raw];
}

/**
* Map normalized items, returning undefined for empty input
* (keeps fromAbapGit symmetric with toAbapGit which omits empty tables)
*/
export function mapItems<T, R>(
items: T[],
fn: (item: T) => R,
): R[] | undefined {
return items.length ? items.map(fn) : undefined;
}

/**
* Unwrap the ADK payload: AdkObject instances store the payload under
* `.data`, plain literals carry fields directly. A literal `data` field
* that is an array (e.g. SPRX) is left untouched.
*/
export function unwrapData<T>(raw: T | { data?: unknown }): T {
const d = (raw as { data?: unknown }).data;
return d !== null && typeof d === 'object' && !Array.isArray(d)
? (d as T)
: (raw as T);
}

/**
* Create an object handler from ADK class
*/
Expand Down
198 changes: 198 additions & 0 deletions packages/adt-plugin-abapgit/src/lib/handlers/objects/enho.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/**
* ENHO (Enhancement Implementation) object handler for abapGit format
*
* Enhancement implementations are XML-based with multiple tool sub-types:
* - BADI_IMPL: BAdI implementation (most common, ADK-supported via AdkBadi)
* - HOOK_IMPL: Hook implementation (source-driven)
* - CLASS: Class enhancement (source-driven)
* - INTF: Interface enhancement
* - WDYC: Web Dynpro component enhancement
* - FUGR: Function group enhancement
* - WDYN: Web Dynpro application enhancement
*
* File layout:
* src/zfoo.enho.xml — metadata (TOOL, SHORTTEXT, IMPL/ENHANCEMENTS, etc.)
* src/zfoo.enho.abap — source (for HOOK_IMPL, CLASS sub-types)
*/

import { enho } from '../../../schemas/generated';
import { createHandler, normalizeItems } from '../base';

type BadiImplData = {
SPOT_NAME?: string;
BADI_NAME?: string;
IMPL_NAME?: string;
IMPL_CLASS?: string;
ACTIVE?: string;
IMPL_SHORTTEXT?: string;
IMPL_SHORTTEXT_ID?: string;
LOCKED_IN_CUSTOMIZING?: string;
FILTER_ROOT?: unknown;
FILTER_VALUES?: unknown;
FILTERS?: unknown;
};

type BadiImplLike = {
spotName?: string;
badiName?: string;
implName?: string;
implClass?: string;
active?: boolean;
implShorttext?: string;
implShorttextId?: string;
lockedInCustomizing?: boolean;
filterRoot?: unknown;
filterValues?: unknown;
filters?: unknown;
};

type EnhancementImplementationLike = {
name: string;
description?: string;
tool?: string;
spotName?: string;
badiName?: string;
implName?: string;
implClass?: string;
active?: boolean;
implShorttext?: string;
implShorttextId?: string;
lockedInCustomizing?: boolean;
filterRoot?: unknown;
filterValues?: unknown;
filters?: unknown;
impls?: BadiImplLike[];
originalObject?: {
pgmid?: string;
objType?: string;
objName?: string;
mainType?: string;
mainName?: string;
programName?: string;
};
enhancements?: unknown;
files?: unknown;
sotr?: unknown;
sotrUse?: unknown;
getSource?: () => Promise<string> | string;
};

export const enhancementImplementationHandler = createHandler<
EnhancementImplementationLike,
typeof enho
>('ENHO', {
schema: enho,
version: 'v1.0.0',
serializer: 'LCL_OBJECT_ENHO',
serializer_version: 'v1.0.0',

toAbapGit: (obj) => {
const tool = obj.tool || 'BADI_IMPL';
const result: Record<string, unknown> = {
TOOL: tool,
SHORTTEXT: obj.description ?? '',
};

if (tool === 'BADI_IMPL') {
const toImplXml = (i: BadiImplLike): BadiImplData => ({
SPOT_NAME: i.spotName,
BADI_NAME: i.badiName,
IMPL_NAME: i.implName,
IMPL_CLASS: i.implClass,
ACTIVE: i.active ? 'X' : undefined,
IMPL_SHORTTEXT: i.implShorttext,
IMPL_SHORTTEXT_ID: i.implShorttextId,
LOCKED_IN_CUSTOMIZING: i.lockedInCustomizing ? 'X' : undefined,
FILTER_ROOT: i.filterRoot,
FILTER_VALUES: i.filterValues,
FILTERS: i.filters,
});
const impls = obj.impls?.length ? obj.impls : [obj];
result.SPOT_NAME = obj.spotName;
result.IMPL = { ENH_BADI_IMPL_DATA: impls.map(toImplXml) };
} else if (obj.originalObject) {
result.ORIGINAL_OBJECT = {
PGMID: obj.originalObject.pgmid ?? 'R3TR',
ORG_OBJ_TYPE: obj.originalObject.objType,
ORG_OBJ_NAME: obj.originalObject.objName,
ORG_MAIN_TYPE: obj.originalObject.mainType,
ORG_MAIN_NAME: obj.originalObject.mainName,
PROGRAMNAME: obj.originalObject.programName,
};
}

// Pass through enhancement metadata shared by all tool sub-types
result.ENHANCEMENTS = obj.enhancements;
result.FILES = obj.files;
result.SOTR = obj.sotr;
result.SOTR_USE = obj.sotrUse;

return result;
},

fromAbapGit: ({
TOOL,
SHORTTEXT,
SPOT_NAME,
IMPL,
ORIGINAL_OBJECT,
ENHANCEMENTS,
FILES,
SOTR,
SOTR_USE,
}) => {
const implRows = normalizeItems(
(
IMPL as
{ ENH_BADI_IMPL_DATA?: BadiImplData | BadiImplData[] } | undefined
)?.ENH_BADI_IMPL_DATA,
);
const implData = implRows[0];
return {
name: '', // ENHO name comes from filename, not XML content
description: SHORTTEXT,
tool: TOOL,
spotName: SPOT_NAME || implData?.SPOT_NAME,
badiName: implData?.BADI_NAME,
implName: implData?.IMPL_NAME,
implClass: implData?.IMPL_CLASS,
active: implData?.ACTIVE === 'X',
implShorttext: implData?.IMPL_SHORTTEXT,
implShorttextId: implData?.IMPL_SHORTTEXT_ID,
lockedInCustomizing: implData?.LOCKED_IN_CUSTOMIZING === 'X',
filterRoot: implData?.FILTER_ROOT,
filterValues: implData?.FILTER_VALUES,
filters: implData?.FILTERS,
impls: implRows.map((i) => ({
spotName: i.SPOT_NAME,
badiName: i.BADI_NAME,
implName: i.IMPL_NAME,
implClass: i.IMPL_CLASS,
active: i.ACTIVE === 'X',
implShorttext: i.IMPL_SHORTTEXT,
implShorttextId: i.IMPL_SHORTTEXT_ID,
lockedInCustomizing: i.LOCKED_IN_CUSTOMIZING === 'X',
filterRoot: i.FILTER_ROOT,
filterValues: i.FILTER_VALUES,
filters: i.FILTERS,
})),
originalObject: ORIGINAL_OBJECT
? {
pgmid: ORIGINAL_OBJECT.PGMID,
objType: ORIGINAL_OBJECT.ORG_OBJ_TYPE,
objName: ORIGINAL_OBJECT.ORG_OBJ_NAME,
mainType: ORIGINAL_OBJECT.ORG_MAIN_TYPE,
mainName: ORIGINAL_OBJECT.ORG_MAIN_NAME,
programName: ORIGINAL_OBJECT.PROGRAMNAME,
}
: undefined,
enhancements: ENHANCEMENTS,
files: FILES,
sotr: SOTR,
sotrUse: SOTR_USE,
};
},

// HOOK_IMPL / CLASS enhancements carry .enho.abap source
getSource: (obj) => Promise.resolve(obj.getSource?.() ?? ''),
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading
Loading