@abapify/adt-lint 0.0.0 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,13 @@
1
1
  # @abapify/adt-lint
2
+
3
+ Shared ABAP lint and source-shaping utilities built on top of `@abaplint/core`.
4
+
5
+ ## Exports
6
+
7
+ - `lintSource(source, options)`
8
+ - `lintAndFix(source, options)`
9
+ - `listRules(options)`
10
+ - `buildPreset(systemType)`
11
+ - `stripToPublicApi(source, objectType)`
12
+ - `extractDependencies(source)`
13
+ - `detectMethodBoundary(source, methodName)`
@@ -0,0 +1,56 @@
1
+ //#region src/lib/types.d.ts
2
+ type LintSeverity = 'error' | 'warning' | 'info';
3
+ interface LintDiagnostic {
4
+ key: string;
5
+ message: string;
6
+ severity: LintSeverity;
7
+ filename: string;
8
+ start: {
9
+ line: number;
10
+ column: number;
11
+ };
12
+ end: {
13
+ line: number;
14
+ column: number;
15
+ };
16
+ }
17
+ type LintSystemType = 'btp' | 'onpremise';
18
+ interface LintOptions {
19
+ filename?: string;
20
+ systemType?: LintSystemType;
21
+ ruleOverrides?: Record<string, unknown>;
22
+ }
23
+ interface RuleInfo {
24
+ key: string;
25
+ enabled: boolean;
26
+ config?: unknown;
27
+ }
28
+ interface MethodBoundary {
29
+ startLine: number;
30
+ endLine: number;
31
+ }
32
+ interface StripResult {
33
+ source: string;
34
+ fallback: boolean;
35
+ }
36
+ //#endregion
37
+ //#region src/lib/preset.d.ts
38
+ declare function buildPreset(systemType: LintSystemType): Record<string, unknown>;
39
+ //#endregion
40
+ //#region src/lib/lint.d.ts
41
+ declare function lintSource(source: string, options?: LintOptions): LintDiagnostic[];
42
+ declare function lintAndFix(source: string, options?: LintOptions): {
43
+ source: string;
44
+ remaining: LintDiagnostic[];
45
+ };
46
+ declare function listRules(options?: LintOptions): RuleInfo[];
47
+ //#endregion
48
+ //#region src/lib/context.d.ts
49
+ type SourceObjectType = 'CLAS' | 'INTF' | 'FUNC' | 'DDLS' | 'PROG' | string;
50
+ declare function stripToPublicApi(source: string, objectType: SourceObjectType): StripResult;
51
+ declare function extractDependencies(source: string): string[];
52
+ declare function normalizeMethodBody(source: string, methodName: string): string;
53
+ declare function detectMethodBoundary(source: string, methodName: string): MethodBoundary | null;
54
+ //#endregion
55
+ export { type LintDiagnostic, type LintOptions, type LintSeverity, type LintSystemType, type MethodBoundary, type RuleInfo, type StripResult, buildPreset, detectMethodBoundary, extractDependencies, lintAndFix, lintSource, listRules, normalizeMethodBody, stripToPublicApi };
56
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/types.ts","../src/lib/preset.ts","../src/lib/lint.ts","../src/lib/context.ts"],"mappings":";KAAY;UAEK;EACf;EACA;EACA,UAAU;EACV;EACA;IACE;IACA;;EAEF;IACE;IACA;;;KAIQ;UAEK;EACf;EACA,aAAa;EACb,gBAAgB;;UAGD;EACf;EACA;EACA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;;;;iBC5Bc,YACd,YAAY,iBACX;;;iBCgEa,WACd,gBACA,UAAU,cACT;iBAKa,WACd,gBACA,UAAU;EAEV;EACA,WAAW;;iBAmBG,UAAU,UAAU,cAAc;;;KC1G7C;iBAkFW,iBACd,gBACA,YAAY,mBACX;iBA6Ba,oBAAoB;iBAsFpB,oBACd,gBACA;iBAgBc,qBACd,gBACA,qBACC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,269 @@
1
+ import { ArtifactsRules, Config, Edits, MemoryFile, Registry, Severity } from "@abaplint/core";
2
+
3
+ //#region src/lib/preset.ts
4
+ function cloneDefaultConfig() {
5
+ return JSON.parse(JSON.stringify(Config.getDefault().get()));
6
+ }
7
+ function buildPreset(systemType) {
8
+ const config = cloneDefaultConfig();
9
+ config.rules ??= {};
10
+ if (systemType === "btp") {
11
+ config.rules.cloud_types = {
12
+ ...config.rules.cloud_types ?? {},
13
+ severity: "Error"
14
+ };
15
+ config.rules.strict_sql = {
16
+ ...config.rules.strict_sql ?? {},
17
+ severity: "Error"
18
+ };
19
+ } else {
20
+ delete config.rules.cloud_types;
21
+ config.rules.obsolete_statement = {
22
+ ...config.rules.obsolete_statement ?? {},
23
+ severity: "Warning"
24
+ };
25
+ }
26
+ return config;
27
+ }
28
+
29
+ //#endregion
30
+ //#region src/lib/lint.ts
31
+ const FALLBACK_FILENAME = "anonymous.prog.abap";
32
+ function toSeverity(severity) {
33
+ switch (severity) {
34
+ case Severity.Warning: return "warning";
35
+ case Severity.Info: return "info";
36
+ default: return "error";
37
+ }
38
+ }
39
+ function toDiagnostics(registry) {
40
+ return registry.findIssues().map((issue) => ({
41
+ key: issue.getKey(),
42
+ message: issue.getMessage(),
43
+ severity: toSeverity(issue.getSeverity()),
44
+ filename: issue.getFilename(),
45
+ start: {
46
+ line: issue.getStart().getRow(),
47
+ column: issue.getStart().getCol()
48
+ },
49
+ end: {
50
+ line: issue.getEnd().getRow(),
51
+ column: issue.getEnd().getCol()
52
+ }
53
+ }));
54
+ }
55
+ function buildConfig(options) {
56
+ const base = buildPreset(options?.systemType ?? "onpremise");
57
+ if (options?.ruleOverrides) {
58
+ base.rules ??= {};
59
+ base.rules = {
60
+ ...base.rules,
61
+ ...options.ruleOverrides
62
+ };
63
+ }
64
+ return new Config(JSON.stringify(base));
65
+ }
66
+ function createRegistry(source, options) {
67
+ const filename = options?.filename ?? FALLBACK_FILENAME;
68
+ const config = buildConfig(options);
69
+ const registry = new Registry(config);
70
+ registry.addFile(new MemoryFile(filename, source));
71
+ registry.parse();
72
+ return {
73
+ registry,
74
+ filename,
75
+ config
76
+ };
77
+ }
78
+ function lintSource(source, options) {
79
+ const { registry } = createRegistry(source, options);
80
+ return toDiagnostics(registry);
81
+ }
82
+ function lintAndFix(source, options) {
83
+ const { registry, filename } = createRegistry(source, options);
84
+ const fixes = registry.findIssues().map((issue) => issue.getDefaultFix()).filter((fix) => Boolean(fix));
85
+ if (fixes.length > 0) {
86
+ Edits.applyEditList(registry, fixes);
87
+ registry.parse();
88
+ }
89
+ return {
90
+ source: registry.getFileByName(filename)?.getRaw() ?? source,
91
+ remaining: toDiagnostics(registry)
92
+ };
93
+ }
94
+ function listRules(options) {
95
+ const config = buildConfig(options);
96
+ const enabled = new Set(config.getEnabledRules().map((rule) => rule.getMetadata().key));
97
+ return ArtifactsRules.getRules().map((rule) => {
98
+ const key = rule.getMetadata().key;
99
+ return {
100
+ key,
101
+ enabled: enabled.has(key),
102
+ config: config.readByRule(key)
103
+ };
104
+ }).sort((a, b) => a.key.localeCompare(b.key));
105
+ }
106
+
107
+ //#endregion
108
+ //#region src/lib/context.ts
109
+ function stripClassSource(source) {
110
+ const lines = source.split(/\r?\n/);
111
+ const output = [];
112
+ let inDefinition = false;
113
+ let inHiddenSection = false;
114
+ let foundDefinition = false;
115
+ for (const line of lines) {
116
+ const upper = line.trim().toUpperCase();
117
+ if (!inDefinition) {
118
+ if (upper.startsWith("CLASS ") && upper.includes(" DEFINITION") && upper.endsWith(".")) {
119
+ inDefinition = true;
120
+ foundDefinition = true;
121
+ output.push(line);
122
+ }
123
+ continue;
124
+ }
125
+ if (upper === "PROTECTED SECTION." || upper === "PRIVATE SECTION.") {
126
+ inHiddenSection = true;
127
+ continue;
128
+ }
129
+ if (upper === "PUBLIC SECTION.") {
130
+ inHiddenSection = false;
131
+ output.push(line);
132
+ continue;
133
+ }
134
+ if (upper === "ENDCLASS.") {
135
+ output.push(line);
136
+ break;
137
+ }
138
+ if (!inHiddenSection) output.push(line);
139
+ }
140
+ if (!foundDefinition || output.length === 0) return {
141
+ source,
142
+ fallback: true
143
+ };
144
+ return {
145
+ source: `${output.join("\n").trim()}\n`,
146
+ fallback: false
147
+ };
148
+ }
149
+ function stripFunctionSource(source) {
150
+ const lines = source.split(/\r?\n/);
151
+ const start = lines.findIndex((line) => /^\s*FUNCTION\s+/i.test(line));
152
+ const end = lines.findIndex((line, index) => index >= start && /^\s*ENDFUNCTION\./i.test(line));
153
+ if (start < 0 || end < 0) return {
154
+ source,
155
+ fallback: true
156
+ };
157
+ return {
158
+ source: `${lines.slice(start, end + 1).filter((line) => {
159
+ const trimmed = line.trim().toUpperCase();
160
+ return trimmed.startsWith("FUNCTION ") || trimmed.startsWith("IMPORTING") || trimmed.startsWith("EXPORTING") || trimmed.startsWith("CHANGING") || trimmed.startsWith("TABLES") || trimmed.startsWith("EXCEPTIONS") || trimmed.startsWith("ENDFUNCTION.") || trimmed === "";
161
+ }).join("\n").trim()}\n`,
162
+ fallback: false
163
+ };
164
+ }
165
+ function stripToPublicApi(source, objectType) {
166
+ const normalized = objectType.toUpperCase();
167
+ if (normalized === "INTF" || normalized === "DDLS" || normalized === "PROG") return {
168
+ source,
169
+ fallback: false
170
+ };
171
+ if (normalized === "CLAS") return stripClassSource(source);
172
+ if (normalized === "FUNC") return stripFunctionSource(source);
173
+ return {
174
+ source,
175
+ fallback: true
176
+ };
177
+ }
178
+ function normalizeToken(token) {
179
+ let t = token;
180
+ while (t.startsWith("'")) t = t.slice(1);
181
+ while (t.endsWith("'")) t = t.slice(0, -1);
182
+ return t.toUpperCase();
183
+ }
184
+ function isCustomName(token) {
185
+ return Boolean(token && /^[ZY][A-Z0-9_]*$/i.test(token));
186
+ }
187
+ function extractDependencies(source) {
188
+ const dependencies = /* @__PURE__ */ new Set();
189
+ const tokens = source.split(/\r?\n/).map((line) => {
190
+ const commentIndex = line.indexOf("\"");
191
+ return commentIndex >= 0 ? line.slice(0, commentIndex) : line;
192
+ }).join("\n").match(/=>|[A-Z_][A-Z0-9_]*|'[^']*'|[.,]/gi) ?? [];
193
+ for (let i = 0; i < tokens.length; i += 1) {
194
+ const t0 = normalizeToken(tokens[i]);
195
+ const t1 = normalizeToken(tokens[i + 1] ?? "");
196
+ const t2 = normalizeToken(tokens[i + 2] ?? "");
197
+ const t3 = normalizeToken(tokens[i + 3] ?? "");
198
+ if (t0 === "TYPE" && t1 === "REF" && t2 === "TO" && isCustomName(t3)) {
199
+ dependencies.add(t3);
200
+ continue;
201
+ }
202
+ if ((t0 === "NEW" || t0 === "CAST") && isCustomName(t1)) {
203
+ dependencies.add(t1);
204
+ continue;
205
+ }
206
+ if (t0 === "INHERITING" && t1 === "FROM" && isCustomName(t2)) {
207
+ dependencies.add(t2);
208
+ continue;
209
+ }
210
+ if (t0 === "CALL" && t1 === "FUNCTION") {
211
+ const fn = normalizeToken(tokens[i + 2] ?? "");
212
+ if (isCustomName(fn)) dependencies.add(fn);
213
+ continue;
214
+ }
215
+ if ((t0 === "RAISING" || t0 === "CATCH") && isCustomName(t1)) {
216
+ dependencies.add(t1);
217
+ continue;
218
+ }
219
+ if (tokens[i + 1] === "=>" && isCustomName(t0)) {
220
+ dependencies.add(t0);
221
+ continue;
222
+ }
223
+ if (t0 === "INTERFACES") for (let j = i + 1; j < tokens.length; j += 1) {
224
+ const next = normalizeToken(tokens[j]);
225
+ if (next === ".") break;
226
+ if (next !== "," && isCustomName(next)) dependencies.add(next);
227
+ }
228
+ }
229
+ return Array.from(dependencies).sort((a, b) => a.localeCompare(b));
230
+ }
231
+ function stripInlineComment(line) {
232
+ const idx = line.indexOf("\"");
233
+ return idx >= 0 ? line.slice(0, idx) : line;
234
+ }
235
+ function extractMethodNameFromHeader(line) {
236
+ const trimmed = stripInlineComment(line).trim();
237
+ const upper = trimmed.toUpperCase();
238
+ if (!upper.startsWith("METHOD ") || !upper.endsWith(".")) return;
239
+ const withoutKeyword = trimmed.slice(7, -1).trim();
240
+ if (!withoutKeyword) return;
241
+ return withoutKeyword.split(/\s+/)[0]?.toUpperCase();
242
+ }
243
+ function normalizeMethodBody(source, methodName) {
244
+ const lines = source.split(/\r?\n/);
245
+ if (lines.length === 0) return source;
246
+ const target = methodName.trim().toUpperCase();
247
+ const firstName = extractMethodNameFromHeader(lines[0] ?? "");
248
+ const last = (lines[lines.length - 1] ?? "").trim().toUpperCase();
249
+ if (firstName === target && last === "ENDMETHOD.") return lines.slice(1, -1).join("\n").trimEnd();
250
+ return source.trimEnd();
251
+ }
252
+ function detectMethodBoundary(source, methodName) {
253
+ const lines = source.split(/\r?\n/);
254
+ const target = methodName.trim().toUpperCase();
255
+ const starts = [];
256
+ for (let i = 0; i < lines.length; i += 1) if (extractMethodNameFromHeader(lines[i] ?? "") === target) starts.push(i);
257
+ if (starts.length === 0) return null;
258
+ if (starts.length > 1) return null;
259
+ const startIndex = starts[0] ?? 0;
260
+ for (let i = startIndex + 1; i < lines.length; i += 1) if (stripInlineComment(lines[i] ?? "").trim().toUpperCase() === "ENDMETHOD.") return {
261
+ startLine: startIndex + 1,
262
+ endLine: i + 1
263
+ };
264
+ return null;
265
+ }
266
+
267
+ //#endregion
268
+ export { buildPreset, detectMethodBoundary, extractDependencies, lintAndFix, lintSource, listRules, normalizeMethodBody, stripToPublicApi };
269
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/preset.ts","../src/lib/lint.ts","../src/lib/context.ts"],"sourcesContent":["import { Config } from '@abaplint/core';\nimport type { LintSystemType } from './types';\n\nfunction cloneDefaultConfig(): Record<string, unknown> {\n return JSON.parse(JSON.stringify(Config.getDefault().get())) as Record<\n string,\n unknown\n >;\n}\n\nexport function buildPreset(\n systemType: LintSystemType,\n): Record<string, unknown> {\n const config: {\n rules?: Record<string, Record<string, unknown>>;\n } = cloneDefaultConfig();\n\n config.rules ??= {};\n\n if (systemType === 'btp') {\n config.rules.cloud_types = {\n ...(config.rules.cloud_types ?? {}),\n severity: 'Error',\n };\n config.rules.strict_sql = {\n ...(config.rules.strict_sql ?? {}),\n severity: 'Error',\n };\n } else {\n delete config.rules.cloud_types;\n config.rules.obsolete_statement = {\n ...(config.rules.obsolete_statement ?? {}),\n severity: 'Warning',\n };\n }\n\n return config;\n}\n","import {\n ArtifactsRules,\n Config,\n Edits,\n MemoryFile,\n Registry,\n Severity,\n} from '@abaplint/core';\nimport { buildPreset } from './preset';\nimport type {\n LintDiagnostic,\n LintOptions,\n LintSeverity,\n RuleInfo,\n} from './types';\n\nconst FALLBACK_FILENAME = 'anonymous.prog.abap';\n\nfunction toSeverity(severity: Severity): LintSeverity {\n switch (severity) {\n case Severity.Warning:\n return 'warning';\n case Severity.Info:\n return 'info';\n default:\n return 'error';\n }\n}\n\nfunction toDiagnostics(registry: Registry): LintDiagnostic[] {\n return registry.findIssues().map((issue) => ({\n key: issue.getKey(),\n message: issue.getMessage(),\n severity: toSeverity(issue.getSeverity()),\n filename: issue.getFilename(),\n start: {\n line: issue.getStart().getRow(),\n column: issue.getStart().getCol(),\n },\n end: {\n line: issue.getEnd().getRow(),\n column: issue.getEnd().getCol(),\n },\n }));\n}\n\nfunction buildConfig(options?: LintOptions): Config {\n const base = buildPreset(options?.systemType ?? 'onpremise') as {\n rules?: Record<string, unknown>;\n };\n\n if (options?.ruleOverrides) {\n base.rules ??= {};\n base.rules = { ...base.rules, ...options.ruleOverrides };\n }\n\n return new Config(JSON.stringify(base));\n}\n\nfunction createRegistry(\n source: string,\n options?: LintOptions,\n): {\n registry: Registry;\n filename: string;\n config: Config;\n} {\n const filename = options?.filename ?? FALLBACK_FILENAME;\n const config = buildConfig(options);\n const registry = new Registry(config);\n registry.addFile(new MemoryFile(filename, source));\n registry.parse();\n\n return { registry, filename, config };\n}\n\nexport function lintSource(\n source: string,\n options?: LintOptions,\n): LintDiagnostic[] {\n const { registry } = createRegistry(source, options);\n return toDiagnostics(registry);\n}\n\nexport function lintAndFix(\n source: string,\n options?: LintOptions,\n): {\n source: string;\n remaining: LintDiagnostic[];\n} {\n const { registry, filename } = createRegistry(source, options);\n const fixes = registry\n .findIssues()\n .map((issue) => issue.getDefaultFix())\n .filter((fix): fix is NonNullable<typeof fix> => Boolean(fix));\n\n if (fixes.length > 0) {\n Edits.applyEditList(registry, fixes);\n registry.parse();\n }\n\n return {\n source: registry.getFileByName(filename)?.getRaw() ?? source,\n remaining: toDiagnostics(registry),\n };\n}\n\nexport function listRules(options?: LintOptions): RuleInfo[] {\n const config = buildConfig(options);\n const enabled = new Set(\n config.getEnabledRules().map((rule) => rule.getMetadata().key),\n );\n\n return ArtifactsRules.getRules()\n .map((rule) => {\n const key = rule.getMetadata().key;\n return {\n key,\n enabled: enabled.has(key),\n config: config.readByRule(key),\n } satisfies RuleInfo;\n })\n .sort((a, b) => a.key.localeCompare(b.key));\n}\n","import type { MethodBoundary, StripResult } from './types';\n\ntype SourceObjectType = 'CLAS' | 'INTF' | 'FUNC' | 'DDLS' | 'PROG' | string;\n\nfunction stripClassSource(source: string): StripResult {\n const lines = source.split(/\\r?\\n/);\n const output: string[] = [];\n\n let inDefinition = false;\n let inHiddenSection = false;\n let foundDefinition = false;\n\n for (const line of lines) {\n const upper = line.trim().toUpperCase();\n\n if (!inDefinition) {\n if (\n upper.startsWith('CLASS ') &&\n upper.includes(' DEFINITION') &&\n upper.endsWith('.')\n ) {\n inDefinition = true;\n foundDefinition = true;\n output.push(line);\n }\n continue;\n }\n\n if (upper === 'PROTECTED SECTION.' || upper === 'PRIVATE SECTION.') {\n inHiddenSection = true;\n continue;\n }\n\n if (upper === 'PUBLIC SECTION.') {\n inHiddenSection = false;\n output.push(line);\n continue;\n }\n\n if (upper === 'ENDCLASS.') {\n output.push(line);\n break;\n }\n\n if (!inHiddenSection) {\n output.push(line);\n }\n }\n\n if (!foundDefinition || output.length === 0) {\n return { source, fallback: true };\n }\n\n return { source: `${output.join('\\n').trim()}\\n`, fallback: false };\n}\n\nfunction stripFunctionSource(source: string): StripResult {\n const lines = source.split(/\\r?\\n/);\n const start = lines.findIndex((line) => /^\\s*FUNCTION\\s+/i.test(line));\n const end = lines.findIndex(\n (line, index) => index >= start && /^\\s*ENDFUNCTION\\./i.test(line),\n );\n\n if (start < 0 || end < 0) {\n return { source, fallback: true };\n }\n\n const kept = lines.slice(start, end + 1).filter((line) => {\n const trimmed = line.trim().toUpperCase();\n return (\n trimmed.startsWith('FUNCTION ') ||\n trimmed.startsWith('IMPORTING') ||\n trimmed.startsWith('EXPORTING') ||\n trimmed.startsWith('CHANGING') ||\n trimmed.startsWith('TABLES') ||\n trimmed.startsWith('EXCEPTIONS') ||\n trimmed.startsWith('ENDFUNCTION.') ||\n trimmed === ''\n );\n });\n\n return { source: `${kept.join('\\n').trim()}\\n`, fallback: false };\n}\n\nexport function stripToPublicApi(\n source: string,\n objectType: SourceObjectType,\n): StripResult {\n const normalized = objectType.toUpperCase();\n\n if (normalized === 'INTF' || normalized === 'DDLS' || normalized === 'PROG') {\n return { source, fallback: false };\n }\n\n if (normalized === 'CLAS') {\n return stripClassSource(source);\n }\n\n if (normalized === 'FUNC') {\n return stripFunctionSource(source);\n }\n\n return { source, fallback: true };\n}\n\nfunction normalizeToken(token: string): string {\n let t = token;\n while (t.startsWith(\"'\")) t = t.slice(1);\n while (t.endsWith(\"'\")) t = t.slice(0, -1);\n return t.toUpperCase();\n}\n\nfunction isCustomName(token: string | undefined): token is string {\n return Boolean(token && /^[ZY][A-Z0-9_]*$/i.test(token));\n}\n\nexport function extractDependencies(source: string): string[] {\n const dependencies = new Set<string>();\n const sanitized = source\n .split(/\\r?\\n/)\n .map((line) => {\n const commentIndex = line.indexOf('\"');\n return commentIndex >= 0 ? line.slice(0, commentIndex) : line;\n })\n .join('\\n');\n const tokens = sanitized.match(/=>|[A-Z_][A-Z0-9_]*|'[^']*'|[.,]/gi) ?? [];\n\n for (let i = 0; i < tokens.length; i += 1) {\n const t0 = normalizeToken(tokens[i]);\n const t1 = normalizeToken(tokens[i + 1] ?? '');\n const t2 = normalizeToken(tokens[i + 2] ?? '');\n const t3 = normalizeToken(tokens[i + 3] ?? '');\n\n if (t0 === 'TYPE' && t1 === 'REF' && t2 === 'TO' && isCustomName(t3)) {\n dependencies.add(t3);\n continue;\n }\n\n if ((t0 === 'NEW' || t0 === 'CAST') && isCustomName(t1)) {\n dependencies.add(t1);\n continue;\n }\n\n if (t0 === 'INHERITING' && t1 === 'FROM' && isCustomName(t2)) {\n dependencies.add(t2);\n continue;\n }\n\n if (t0 === 'CALL' && t1 === 'FUNCTION') {\n const fn = normalizeToken(tokens[i + 2] ?? '');\n if (isCustomName(fn)) {\n dependencies.add(fn);\n }\n continue;\n }\n\n if ((t0 === 'RAISING' || t0 === 'CATCH') && isCustomName(t1)) {\n dependencies.add(t1);\n continue;\n }\n\n if (tokens[i + 1] === '=>' && isCustomName(t0)) {\n dependencies.add(t0);\n continue;\n }\n\n if (t0 === 'INTERFACES') {\n for (let j = i + 1; j < tokens.length; j += 1) {\n const next = normalizeToken(tokens[j]);\n if (next === '.') {\n break;\n }\n if (next !== ',' && isCustomName(next)) {\n dependencies.add(next);\n }\n }\n }\n }\n\n return Array.from(dependencies).sort((a, b) => a.localeCompare(b));\n}\n\nfunction stripInlineComment(line: string): string {\n const idx = line.indexOf('\"');\n return idx >= 0 ? line.slice(0, idx) : line;\n}\n\nfunction extractMethodNameFromHeader(line: string): string | undefined {\n const trimmed = stripInlineComment(line).trim();\n const upper = trimmed.toUpperCase();\n if (!upper.startsWith('METHOD ') || !upper.endsWith('.')) {\n return undefined;\n }\n\n const withoutKeyword = trimmed.slice('METHOD '.length, -1).trim();\n if (!withoutKeyword) {\n return undefined;\n }\n\n return withoutKeyword.split(/\\s+/)[0]?.toUpperCase();\n}\n\nexport function normalizeMethodBody(\n source: string,\n methodName: string,\n): string {\n const lines = source.split(/\\r?\\n/);\n if (lines.length === 0) return source;\n\n const target = methodName.trim().toUpperCase();\n const firstName = extractMethodNameFromHeader(lines[0] ?? '');\n const last = (lines[lines.length - 1] ?? '').trim().toUpperCase();\n\n if (firstName === target && last === 'ENDMETHOD.') {\n return lines.slice(1, -1).join('\\n').trimEnd();\n }\n\n return source.trimEnd();\n}\n\nexport function detectMethodBoundary(\n source: string,\n methodName: string,\n): MethodBoundary | null {\n const lines = source.split(/\\r?\\n/);\n const target = methodName.trim().toUpperCase();\n\n const starts: number[] = [];\n for (let i = 0; i < lines.length; i += 1) {\n if (extractMethodNameFromHeader(lines[i] ?? '') === target) {\n starts.push(i);\n }\n }\n\n if (starts.length === 0) {\n return null;\n }\n\n if (starts.length > 1) {\n return null;\n }\n\n const startIndex = starts[0] ?? 0;\n for (let i = startIndex + 1; i < lines.length; i += 1) {\n if (\n stripInlineComment(lines[i] ?? '')\n .trim()\n .toUpperCase() === 'ENDMETHOD.'\n ) {\n return {\n startLine: startIndex + 1,\n endLine: i + 1,\n };\n }\n }\n\n return null;\n}\n"],"mappings":";;;AAGA,SAAS,qBAA8C;CACrD,OAAO,KAAK,MAAM,KAAK,UAAU,OAAO,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC;AAI7D;AAEA,SAAgB,YACd,YACyB;CACzB,MAAM,SAEF,mBAAmB;CAEvB,OAAO,UAAU,CAAC;CAElB,IAAI,eAAe,OAAO;EACxB,OAAO,MAAM,cAAc;GACzB,GAAI,OAAO,MAAM,eAAe,CAAC;GACjC,UAAU;EACZ;EACA,OAAO,MAAM,aAAa;GACxB,GAAI,OAAO,MAAM,cAAc,CAAC;GAChC,UAAU;EACZ;CACF,OAAO;EACL,OAAO,OAAO,MAAM;EACpB,OAAO,MAAM,qBAAqB;GAChC,GAAI,OAAO,MAAM,sBAAsB,CAAC;GACxC,UAAU;EACZ;CACF;CAEA,OAAO;AACT;;;;ACrBA,MAAM,oBAAoB;AAE1B,SAAS,WAAW,UAAkC;CACpD,QAAQ,UAAR;EACE,KAAK,SAAS,SACZ,OAAO;EACT,KAAK,SAAS,MACZ,OAAO;EACT,SACE,OAAO;CACX;AACF;AAEA,SAAS,cAAc,UAAsC;CAC3D,OAAO,SAAS,WAAW,CAAC,CAAC,KAAK,WAAW;EAC3C,KAAK,MAAM,OAAO;EAClB,SAAS,MAAM,WAAW;EAC1B,UAAU,WAAW,MAAM,YAAY,CAAC;EACxC,UAAU,MAAM,YAAY;EAC5B,OAAO;GACL,MAAM,MAAM,SAAS,CAAC,CAAC,OAAO;GAC9B,QAAQ,MAAM,SAAS,CAAC,CAAC,OAAO;EAClC;EACA,KAAK;GACH,MAAM,MAAM,OAAO,CAAC,CAAC,OAAO;GAC5B,QAAQ,MAAM,OAAO,CAAC,CAAC,OAAO;EAChC;CACF,EAAE;AACJ;AAEA,SAAS,YAAY,SAA+B;CAClD,MAAM,OAAO,YAAY,SAAS,cAAc,WAAW;CAI3D,IAAI,SAAS,eAAe;EAC1B,KAAK,UAAU,CAAC;EAChB,KAAK,QAAQ;GAAE,GAAG,KAAK;GAAO,GAAG,QAAQ;EAAc;CACzD;CAEA,OAAO,IAAI,OAAO,KAAK,UAAU,IAAI,CAAC;AACxC;AAEA,SAAS,eACP,QACA,SAKA;CACA,MAAM,WAAW,SAAS,YAAY;CACtC,MAAM,SAAS,YAAY,OAAO;CAClC,MAAM,WAAW,IAAI,SAAS,MAAM;CACpC,SAAS,QAAQ,IAAI,WAAW,UAAU,MAAM,CAAC;CACjD,SAAS,MAAM;CAEf,OAAO;EAAE;EAAU;EAAU;CAAO;AACtC;AAEA,SAAgB,WACd,QACA,SACkB;CAClB,MAAM,EAAE,aAAa,eAAe,QAAQ,OAAO;CACnD,OAAO,cAAc,QAAQ;AAC/B;AAEA,SAAgB,WACd,QACA,SAIA;CACA,MAAM,EAAE,UAAU,aAAa,eAAe,QAAQ,OAAO;CAC7D,MAAM,QAAQ,SACX,WAAW,CAAC,CACZ,KAAK,UAAU,MAAM,cAAc,CAAC,CAAC,CACrC,QAAQ,QAAwC,QAAQ,GAAG,CAAC;CAE/D,IAAI,MAAM,SAAS,GAAG;EACpB,MAAM,cAAc,UAAU,KAAK;EACnC,SAAS,MAAM;CACjB;CAEA,OAAO;EACL,QAAQ,SAAS,cAAc,QAAQ,CAAC,EAAE,OAAO,KAAK;EACtD,WAAW,cAAc,QAAQ;CACnC;AACF;AAEA,SAAgB,UAAU,SAAmC;CAC3D,MAAM,SAAS,YAAY,OAAO;CAClC,MAAM,UAAU,IAAI,IAClB,OAAO,gBAAgB,CAAC,CAAC,KAAK,SAAS,KAAK,YAAY,CAAC,CAAC,GAAG,CAC/D;CAEA,OAAO,eAAe,SAAS,CAAC,CAC7B,KAAK,SAAS;EACb,MAAM,MAAM,KAAK,YAAY,CAAC,CAAC;EAC/B,OAAO;GACL;GACA,SAAS,QAAQ,IAAI,GAAG;GACxB,QAAQ,OAAO,WAAW,GAAG;EAC/B;CACF,CAAC,CAAC,CACD,MAAM,GAAG,MAAM,EAAE,IAAI,cAAc,EAAE,GAAG,CAAC;AAC9C;;;;ACxHA,SAAS,iBAAiB,QAA6B;CACrD,MAAM,QAAQ,OAAO,MAAM,OAAO;CAClC,MAAM,SAAmB,CAAC;CAE1B,IAAI,eAAe;CACnB,IAAI,kBAAkB;CACtB,IAAI,kBAAkB;CAEtB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,KAAK,KAAK,CAAC,CAAC,YAAY;EAEtC,IAAI,CAAC,cAAc;GACjB,IACE,MAAM,WAAW,QAAQ,KACzB,MAAM,SAAS,aAAa,KAC5B,MAAM,SAAS,GAAG,GAClB;IACA,eAAe;IACf,kBAAkB;IAClB,OAAO,KAAK,IAAI;GAClB;GACA;EACF;EAEA,IAAI,UAAU,wBAAwB,UAAU,oBAAoB;GAClE,kBAAkB;GAClB;EACF;EAEA,IAAI,UAAU,mBAAmB;GAC/B,kBAAkB;GAClB,OAAO,KAAK,IAAI;GAChB;EACF;EAEA,IAAI,UAAU,aAAa;GACzB,OAAO,KAAK,IAAI;GAChB;EACF;EAEA,IAAI,CAAC,iBACH,OAAO,KAAK,IAAI;CAEpB;CAEA,IAAI,CAAC,mBAAmB,OAAO,WAAW,GACxC,OAAO;EAAE;EAAQ,UAAU;CAAK;CAGlC,OAAO;EAAE,QAAQ,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE;EAAK,UAAU;CAAM;AACpE;AAEA,SAAS,oBAAoB,QAA6B;CACxD,MAAM,QAAQ,OAAO,MAAM,OAAO;CAClC,MAAM,QAAQ,MAAM,WAAW,SAAS,mBAAmB,KAAK,IAAI,CAAC;CACrE,MAAM,MAAM,MAAM,WACf,MAAM,UAAU,SAAS,SAAS,qBAAqB,KAAK,IAAI,CACnE;CAEA,IAAI,QAAQ,KAAK,MAAM,GACrB,OAAO;EAAE;EAAQ,UAAU;CAAK;CAiBlC,OAAO;EAAE,QAAQ,GAdJ,MAAM,MAAM,OAAO,MAAM,CAAC,CAAC,CAAC,QAAQ,SAAS;GACxD,MAAM,UAAU,KAAK,KAAK,CAAC,CAAC,YAAY;GACxC,OACE,QAAQ,WAAW,WAAW,KAC9B,QAAQ,WAAW,WAAW,KAC9B,QAAQ,WAAW,WAAW,KAC9B,QAAQ,WAAW,UAAU,KAC7B,QAAQ,WAAW,QAAQ,KAC3B,QAAQ,WAAW,YAAY,KAC/B,QAAQ,WAAW,cAAc,KACjC,YAAY;EAEhB,CAEuB,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE;EAAK,UAAU;CAAM;AAClE;AAEA,SAAgB,iBACd,QACA,YACa;CACb,MAAM,aAAa,WAAW,YAAY;CAE1C,IAAI,eAAe,UAAU,eAAe,UAAU,eAAe,QACnE,OAAO;EAAE;EAAQ,UAAU;CAAM;CAGnC,IAAI,eAAe,QACjB,OAAO,iBAAiB,MAAM;CAGhC,IAAI,eAAe,QACjB,OAAO,oBAAoB,MAAM;CAGnC,OAAO;EAAE;EAAQ,UAAU;CAAK;AAClC;AAEA,SAAS,eAAe,OAAuB;CAC7C,IAAI,IAAI;CACR,OAAO,EAAE,WAAW,GAAG,GAAG,IAAI,EAAE,MAAM,CAAC;CACvC,OAAO,EAAE,SAAS,GAAG,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE;CACzC,OAAO,EAAE,YAAY;AACvB;AAEA,SAAS,aAAa,OAA4C;CAChE,OAAO,QAAQ,SAAS,oBAAoB,KAAK,KAAK,CAAC;AACzD;AAEA,SAAgB,oBAAoB,QAA0B;CAC5D,MAAM,+BAAe,IAAI,IAAY;CAQrC,MAAM,SAPY,OACf,MAAM,OAAO,CAAC,CACd,KAAK,SAAS;EACb,MAAM,eAAe,KAAK,QAAQ,IAAG;EACrC,OAAO,gBAAgB,IAAI,KAAK,MAAM,GAAG,YAAY,IAAI;CAC3D,CAAC,CAAC,CACD,KAAK,IACe,CAAC,CAAC,MAAM,oCAAoC,KAAK,CAAC;CAEzE,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;EACzC,MAAM,KAAK,eAAe,OAAO,EAAE;EACnC,MAAM,KAAK,eAAe,OAAO,IAAI,MAAM,EAAE;EAC7C,MAAM,KAAK,eAAe,OAAO,IAAI,MAAM,EAAE;EAC7C,MAAM,KAAK,eAAe,OAAO,IAAI,MAAM,EAAE;EAE7C,IAAI,OAAO,UAAU,OAAO,SAAS,OAAO,QAAQ,aAAa,EAAE,GAAG;GACpE,aAAa,IAAI,EAAE;GACnB;EACF;EAEA,KAAK,OAAO,SAAS,OAAO,WAAW,aAAa,EAAE,GAAG;GACvD,aAAa,IAAI,EAAE;GACnB;EACF;EAEA,IAAI,OAAO,gBAAgB,OAAO,UAAU,aAAa,EAAE,GAAG;GAC5D,aAAa,IAAI,EAAE;GACnB;EACF;EAEA,IAAI,OAAO,UAAU,OAAO,YAAY;GACtC,MAAM,KAAK,eAAe,OAAO,IAAI,MAAM,EAAE;GAC7C,IAAI,aAAa,EAAE,GACjB,aAAa,IAAI,EAAE;GAErB;EACF;EAEA,KAAK,OAAO,aAAa,OAAO,YAAY,aAAa,EAAE,GAAG;GAC5D,aAAa,IAAI,EAAE;GACnB;EACF;EAEA,IAAI,OAAO,IAAI,OAAO,QAAQ,aAAa,EAAE,GAAG;GAC9C,aAAa,IAAI,EAAE;GACnB;EACF;EAEA,IAAI,OAAO,cACT,KAAK,IAAI,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;GAC7C,MAAM,OAAO,eAAe,OAAO,EAAE;GACrC,IAAI,SAAS,KACX;GAEF,IAAI,SAAS,OAAO,aAAa,IAAI,GACnC,aAAa,IAAI,IAAI;EAEzB;CAEJ;CAEA,OAAO,MAAM,KAAK,YAAY,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AACnE;AAEA,SAAS,mBAAmB,MAAsB;CAChD,MAAM,MAAM,KAAK,QAAQ,IAAG;CAC5B,OAAO,OAAO,IAAI,KAAK,MAAM,GAAG,GAAG,IAAI;AACzC;AAEA,SAAS,4BAA4B,MAAkC;CACrE,MAAM,UAAU,mBAAmB,IAAI,CAAC,CAAC,KAAK;CAC9C,MAAM,QAAQ,QAAQ,YAAY;CAClC,IAAI,CAAC,MAAM,WAAW,SAAS,KAAK,CAAC,MAAM,SAAS,GAAG,GACrD;CAGF,MAAM,iBAAiB,QAAQ,MAAM,GAAkB,EAAE,CAAC,CAAC,KAAK;CAChE,IAAI,CAAC,gBACH;CAGF,OAAO,eAAe,MAAM,KAAK,CAAC,CAAC,EAAE,EAAE,YAAY;AACrD;AAEA,SAAgB,oBACd,QACA,YACQ;CACR,MAAM,QAAQ,OAAO,MAAM,OAAO;CAClC,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,MAAM,SAAS,WAAW,KAAK,CAAC,CAAC,YAAY;CAC7C,MAAM,YAAY,4BAA4B,MAAM,MAAM,EAAE;CAC5D,MAAM,QAAQ,MAAM,MAAM,SAAS,MAAM,GAAE,CAAE,KAAK,CAAC,CAAC,YAAY;CAEhE,IAAI,cAAc,UAAU,SAAS,cACnC,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ;CAG/C,OAAO,OAAO,QAAQ;AACxB;AAEA,SAAgB,qBACd,QACA,YACuB;CACvB,MAAM,QAAQ,OAAO,MAAM,OAAO;CAClC,MAAM,SAAS,WAAW,KAAK,CAAC,CAAC,YAAY;CAE7C,MAAM,SAAmB,CAAC;CAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GACrC,IAAI,4BAA4B,MAAM,MAAM,EAAE,MAAM,QAClD,OAAO,KAAK,CAAC;CAIjB,IAAI,OAAO,WAAW,GACpB,OAAO;CAGT,IAAI,OAAO,SAAS,GAClB,OAAO;CAGT,MAAM,aAAa,OAAO,MAAM;CAChC,KAAK,IAAI,IAAI,aAAa,GAAG,IAAI,MAAM,QAAQ,KAAK,GAClD,IACE,mBAAmB,MAAM,MAAM,EAAE,CAAC,CAC/B,KAAK,CAAC,CACN,YAAY,MAAM,cAErB,OAAO;EACL,WAAW,aAAa;EACxB,SAAS,IAAI;CACf;CAIJ,OAAO;AACT"}
package/package.json CHANGED
@@ -1 +1,31 @@
1
- {"name":"@abapify/adt-lint","version":"0.0.0","description":"Placeholder — real package is published via CI/CD (trusted publishing)."}
1
+ {
2
+ "name": "@abapify/adt-lint",
3
+ "version": "0.4.4",
4
+ "description": "Shared ABAP lint and source-shaping utilities built on @abaplint/core",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "types": "./dist/index.d.mts",
11
+ "exports": {
12
+ ".": "./dist/index.mjs",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "dependencies": {
20
+ "@abaplint/core": "^2.119.15"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/abapify/adt-cli.git",
25
+ "directory": "packages/adt-lint"
26
+ },
27
+ "homepage": "https://github.com/abapify/adt-cli/tree/main/packages/adt-lint#readme",
28
+ "bugs": {
29
+ "url": "https://github.com/abapify/adt-cli/issues"
30
+ }
31
+ }