hana-linter 1.0.1 → 1.0.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
+ ### 1.0.2
8
+
9
+ - Added Chevrotain-based `.hdbfunction` parser; content-linting of function input parameters is now fully reliable
10
+ - The `.hdbfunction` parser isolates the parameter list from the `RETURNS` clause and the function body (`AS BEGIN … END`), eliminating false positives caused by `IN` keywords inside SQL body statements and column names in `RETURNS TABLE (...)` definitions
11
+ - HANA functions accept only `IN` parameters; the new parser correctly produces only `inputParameter` subjects and never emits `outputParameter` entries for `.hdbfunction` files
12
+ - Removed the shared ad-hoc regex extractor (`extractProcedureFunctionParameters`) that was previously used for both `.hdbprocedure` and `.hdbfunction` files
13
+
14
+ ---
15
+
7
16
  ### 1.0.0
8
17
 
9
18
  - Replaced ad-hoc, line-by-line regex extraction with Chevrotain-based lexer + CST parsers for `.hdbtable`, `.hdbprocedure`, and `.hdbview` files
package/README.md CHANGED
@@ -46,23 +46,22 @@ Content-based linting uses [Chevrotain](https://chevrotain.io)-powered lexers an
46
46
 
47
47
  > **Work in progress.** Not all artifact types have been migrated to the Chevrotain-based parsing infrastructure yet.
48
48
 
49
- | Artifact extension | Content extractor | Status |
50
- | ------------------------- | ---------------------- | ------------------ |
51
- | `.hdbtable` | Chevrotain lexer + CST | ✅ Migrated |
52
- | `.hdbview` | Chevrotain lexer + CST | ✅ Migrated |
53
- | `.hdbprocedure` | Chevrotain lexer + CST | ✅ Migrated |
54
- | `.hdbfunction` | Inline regex | ⚠️ Needs migration |
55
- | `.hdbtabletype` | — | ❌ Not implemented |
56
- | `.hdbcalculationview` | — | ❌ Not implemented |
57
- | `.hdbanalyticalprivilege` | — | ❌ Not implemented |
58
- | `.hdbrole` | — | ❌ Not implemented |
59
- | `.hdbsequence` | — | ❌ Not implemented |
60
- | `.hdbconstraint` | — | ❌ Not implemented |
61
- | `.hdbschedulerjob` | — | ❌ Not implemented |
62
- | `.hdbindex` | — | ❌ Not implemented |
63
- | `.hdbtrigger` | — | ❌ Not implemented |
64
-
65
- - **Needs migration**: uses an ad-hoc regex scan, subject to false positives from body keywords, multi-line definitions, and block comments.
49
+ | Artifact extension | Content extractor | Status |
50
+ | ------------------------- | ---------------------- | ---------------------- |
51
+ | `.hdbtable` | Chevrotain lexer + CST | ✅ Migrated |
52
+ | `.hdbview` | Chevrotain lexer + CST | ✅ Migrated |
53
+ | `.hdbprocedure` | Chevrotain lexer + CST | ✅ Migrated |
54
+ | `.hdbfunction` | Chevrotain lexer + CST | ✅ Migrated |
55
+ | `.hdbtabletype` | — | ❌ Not yet implemented |
56
+ | `.hdbcalculationview` | — | ❌ Not yet implemented |
57
+ | `.hdbanalyticalprivilege` | — | ❌ Not yet implemented |
58
+ | `.hdbrole` | — | ❌ Not yet implemented |
59
+ | `.hdbsequence` | — | ❌ Not yet implemented |
60
+ | `.hdbconstraint` | — | ❌ Not yet implemented |
61
+ | `.hdbschedulerjob` | — | ❌ Not yet implemented |
62
+ | `.hdbindex` | — | ❌ Not yet implemented |
63
+ | `.hdbtrigger` | — | ❌ Not yet implemented |
64
+
66
65
  - **Not implemented**: `contentRuleSets` targeting these extensions will silently return no results — no identifiers are extracted and no content issues are raised.
67
66
 
68
67
  ## Install
@@ -178,12 +177,13 @@ Each `contentRuleSets` item contains:
178
177
 
179
178
  Supported extractors in this version:
180
179
 
181
- | `target` | Supported extensions | Extracted identifiers |
182
- | ----------------- | ------------------------------- | ---------------------------------------------- |
183
- | `field` | `.hdbtable` | Column names |
184
- | `field` | `.hdbview` | Column aliases (explicit list or `AS` aliases) |
185
- | `inputParameter` | `.hdbprocedure`, `.hdbfunction` | `IN` and `INOUT` parameters |
186
- | `outputParameter` | `.hdbprocedure`, `.hdbfunction` | `OUT` and `INOUT` parameters |
180
+ | `target` | Supported extensions | Extracted identifiers |
181
+ | ----------------- | -------------------- | ---------------------------------------------- |
182
+ | `field` | `.hdbtable` | Column names |
183
+ | `field` | `.hdbview` | Column aliases (explicit list or `AS` aliases) |
184
+ | `inputParameter` | `.hdbprocedure` | `IN` and `INOUT` parameters |
185
+ | `inputParameter` | `.hdbfunction` | `IN` parameters (functions accept `IN` only) |
186
+ | `outputParameter` | `.hdbprocedure` | `OUT` and `INOUT` parameters |
187
187
 
188
188
  ### Default Config Example
189
189
 
@@ -9,6 +9,7 @@ const node_path_1 = __importDefault(require("node:path"));
9
9
  const index_1 = require("./parsers/hdbtable/index");
10
10
  const index_2 = require("./parsers/hdbview/index");
11
11
  const index_3 = require("./parsers/hdbprocedure/index");
12
+ const index_4 = require("./parsers/hdbfunction/index");
12
13
  /**
13
14
  * Run content-based naming lint for a file.
14
15
  *
@@ -55,36 +56,10 @@ function extractSubjects(extension, fileContent) {
55
56
  return (0, index_3.extractProcedureParameters)(fileContent);
56
57
  }
57
58
  if (extension === '.hdbfunction') {
58
- return extractProcedureFunctionParameters(fileContent);
59
+ return (0, index_4.extractFunctionParameters)(fileContent);
59
60
  }
60
61
  return [];
61
62
  }
62
- function extractProcedureFunctionParameters(fileContent) {
63
- const subjects = [];
64
- const seen = new Set();
65
- const parameterRegex = /\b(IN|OUT|INOUT)\s+("?[A-Za-z_][A-Za-z0-9_]*"?)\s+[A-Za-z]/gi;
66
- let match = parameterRegex.exec(fileContent);
67
- while (match) {
68
- const mode = match[1];
69
- const rawName = match[2];
70
- if (!mode || !rawName) {
71
- match = parameterRegex.exec(fileContent);
72
- continue;
73
- }
74
- const normalizedMode = mode.toUpperCase();
75
- const normalizedName = rawName.replace(/^"|"$/g, '');
76
- if ((normalizedMode === 'IN' || normalizedMode === 'INOUT') && !seen.has(`input:${normalizedName}`)) {
77
- seen.add(`input:${normalizedName}`);
78
- subjects.push({ type: 'inputParameter', name: normalizedName });
79
- }
80
- if ((normalizedMode === 'OUT' || normalizedMode === 'INOUT') && !seen.has(`output:${normalizedName}`)) {
81
- seen.add(`output:${normalizedName}`);
82
- subjects.push({ type: 'outputParameter', name: normalizedName });
83
- }
84
- match = parameterRegex.exec(fileContent);
85
- }
86
- return subjects;
87
- }
88
63
  function evaluateAllRules(filePath, extension, subject, rules) {
89
64
  const issues = [];
90
65
  for (const rule of rules) {
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const index_1 = require("../index");
5
+ // ---------------------------------------------------------------------------
6
+ // Helpers
7
+ // ---------------------------------------------------------------------------
8
+ function inputs(ddl) {
9
+ return (0, index_1.extractFunctionParameters)(ddl)
10
+ .filter((s) => s.type === 'inputParameter')
11
+ .map((s) => s.name);
12
+ }
13
+ // ---------------------------------------------------------------------------
14
+ // AC-1 IN parameter extraction (scalar function)
15
+ // ---------------------------------------------------------------------------
16
+ (0, vitest_1.describe)('AC-1: IN parameter extraction (scalar function)', () => {
17
+ (0, vitest_1.it)('extracts IN parameters as inputParameter subjects', () => {
18
+ const ddl = `
19
+ FUNCTION MY_FUNC (
20
+ IN IV_CUSTOMER_ID NVARCHAR(10),
21
+ IN IV_DATE DATE
22
+ ) RETURNS NVARCHAR(100) AS BEGIN END
23
+ `;
24
+ (0, vitest_1.expect)((0, index_1.extractFunctionParameters)(ddl)).toEqual([
25
+ { type: 'inputParameter', name: 'IV_CUSTOMER_ID' },
26
+ { type: 'inputParameter', name: 'IV_DATE' }
27
+ ]);
28
+ });
29
+ (0, vitest_1.it)('does not produce outputParameter entries for any parameter', () => {
30
+ const ddl = `FUNCTION F (IN IV_AMOUNT DECIMAL(18,2)) RETURNS INTEGER AS BEGIN END`;
31
+ const outputSubjects = (0, index_1.extractFunctionParameters)(ddl).filter((s) => s.type === 'outputParameter');
32
+ (0, vitest_1.expect)(outputSubjects).toHaveLength(0);
33
+ });
34
+ (0, vitest_1.it)('all results carry type "inputParameter"', () => {
35
+ const ddl = `FUNCTION F (IN IV_A INTEGER, IN IV_B NVARCHAR(10)) RETURNS INTEGER AS BEGIN END`;
36
+ (0, vitest_1.expect)((0, index_1.extractFunctionParameters)(ddl).every((r) => r.type === 'inputParameter')).toBe(true);
37
+ });
38
+ });
39
+ // ---------------------------------------------------------------------------
40
+ // AC-2 IN parameter extraction (table function)
41
+ // ---------------------------------------------------------------------------
42
+ (0, vitest_1.describe)('AC-2: IN parameter extraction (table function)', () => {
43
+ (0, vitest_1.it)('extracts IN params and ignores RETURNS TABLE columns', () => {
44
+ const ddl = `
45
+ FUNCTION MY_FUNC (
46
+ IN IV_STATUS NVARCHAR(1)
47
+ ) RETURNS TABLE (ID INTEGER, NAME NVARCHAR(100)) AS BEGIN END
48
+ `;
49
+ (0, vitest_1.expect)((0, index_1.extractFunctionParameters)(ddl)).toEqual([{ type: 'inputParameter', name: 'IV_STATUS' }]);
50
+ });
51
+ (0, vitest_1.it)('does not produce outputParameter entries for a table function', () => {
52
+ const ddl = `
53
+ FUNCTION F (IN IV_X INTEGER)
54
+ RETURNS TABLE (COL1 NVARCHAR(10))
55
+ AS BEGIN END
56
+ `;
57
+ const outputSubjects = (0, index_1.extractFunctionParameters)(ddl).filter((s) => s.type === 'outputParameter');
58
+ (0, vitest_1.expect)(outputSubjects).toHaveLength(0);
59
+ });
60
+ });
61
+ // ---------------------------------------------------------------------------
62
+ // AC-3 RETURNS TABLE columns are not extracted
63
+ // ---------------------------------------------------------------------------
64
+ (0, vitest_1.describe)('AC-3: RETURNS TABLE columns not extracted', () => {
65
+ (0, vitest_1.it)('does not extract OUT_COL or IN_COL from RETURNS TABLE definition', () => {
66
+ const ddl = `
67
+ FUNCTION F ()
68
+ RETURNS TABLE (OUT_COL INTEGER, IN_COL NVARCHAR(10))
69
+ AS BEGIN END
70
+ `;
71
+ const names = (0, index_1.extractFunctionParameters)(ddl).map((s) => s.name);
72
+ (0, vitest_1.expect)(names).not.toContain('OUT_COL');
73
+ (0, vitest_1.expect)(names).not.toContain('IN_COL');
74
+ });
75
+ (0, vitest_1.it)('does not extract column names from a multi-column RETURNS TABLE', () => {
76
+ const ddl = `
77
+ FUNCTION F (IN IV_X INTEGER)
78
+ RETURNS TABLE (
79
+ COL_A BIGINT,
80
+ COL_B NVARCHAR(200),
81
+ COL_C TIMESTAMP
82
+ ) AS BEGIN END
83
+ `;
84
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_X']);
85
+ });
86
+ });
87
+ // ---------------------------------------------------------------------------
88
+ // AC-4 No outputParameter subjects produced
89
+ // ---------------------------------------------------------------------------
90
+ (0, vitest_1.describe)('AC-4: no outputParameter subjects produced', () => {
91
+ (0, vitest_1.it)('never emits an outputParameter entry for any parameter', () => {
92
+ const ddl = `
93
+ FUNCTION F (IN IV_A INTEGER, IN IV_B NVARCHAR(10))
94
+ RETURNS INTEGER AS BEGIN END
95
+ `;
96
+ const outputSubjects = (0, index_1.extractFunctionParameters)(ddl).filter((s) => s.type === 'outputParameter');
97
+ (0, vitest_1.expect)(outputSubjects).toHaveLength(0);
98
+ });
99
+ (0, vitest_1.it)('never emits outputParameter even for a function with no parameters', () => {
100
+ const ddl = `FUNCTION F () RETURNS INTEGER AS BEGIN END`;
101
+ const outputSubjects = (0, index_1.extractFunctionParameters)(ddl).filter((s) => s.type === 'outputParameter');
102
+ (0, vitest_1.expect)(outputSubjects).toHaveLength(0);
103
+ });
104
+ });
105
+ // ---------------------------------------------------------------------------
106
+ // AC-5 TABLE-type IN parameter: inner columns not extracted
107
+ // ---------------------------------------------------------------------------
108
+ (0, vitest_1.describe)('AC-5: TABLE-type IN parameter columns not extracted', () => {
109
+ (0, vitest_1.it)('extracts only the outer parameter name, not inner column names', () => {
110
+ const ddl = `
111
+ FUNCTION F (
112
+ IN TV_INPUT TABLE (COL1 INTEGER, COL2 NVARCHAR(100))
113
+ ) RETURNS INTEGER AS BEGIN END
114
+ `;
115
+ const result = (0, index_1.extractFunctionParameters)(ddl);
116
+ (0, vitest_1.expect)(result).toContainEqual({ type: 'inputParameter', name: 'TV_INPUT' });
117
+ (0, vitest_1.expect)(result.map((s) => s.name)).not.toContain('COL1');
118
+ (0, vitest_1.expect)(result.map((s) => s.name)).not.toContain('COL2');
119
+ });
120
+ (0, vitest_1.it)('handles multi-column TABLE-type IN param without extracting column names', () => {
121
+ const ddl = `
122
+ FUNCTION F (
123
+ IN TV_DATA TABLE (
124
+ ID BIGINT,
125
+ NAME NVARCHAR(200),
126
+ CREATED_AT TIMESTAMP
127
+ )
128
+ ) RETURNS INTEGER AS BEGIN END
129
+ `;
130
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['TV_DATA']);
131
+ });
132
+ });
133
+ // ---------------------------------------------------------------------------
134
+ // AC-6 Function body SQL does not pollute extraction
135
+ // ---------------------------------------------------------------------------
136
+ (0, vitest_1.describe)('AC-6: function body SQL does not pollute extraction', () => {
137
+ (0, vitest_1.it)('ignores IN keyword inside WHERE clause in the body', () => {
138
+ const ddl = `
139
+ FUNCTION F (IN IV_ID INTEGER) RETURNS INTEGER AS
140
+ BEGIN
141
+ SELECT COUNT(*) FROM MY_TABLE WHERE STATUS IN ('A', 'B');
142
+ END
143
+ `;
144
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_ID']);
145
+ });
146
+ (0, vitest_1.it)('handles nested BEGIN/END in body without false extraction', () => {
147
+ const ddl = `
148
+ FUNCTION F (IN IV_FLAG BOOLEAN) RETURNS NVARCHAR(10) AS
149
+ BEGIN
150
+ DECLARE result NVARCHAR(10);
151
+ IF IV_FLAG = TRUE THEN
152
+ BEGIN
153
+ result = 'YES';
154
+ END;
155
+ RETURN result;
156
+ END
157
+ `;
158
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_FLAG']);
159
+ });
160
+ (0, vitest_1.it)('body content containing IN and function-like syntax is ignored', () => {
161
+ const ddl = `
162
+ FUNCTION F (IN IV_STATUS NVARCHAR(1), IN IV_ID INTEGER)
163
+ RETURNS TABLE (OUT_COL NVARCHAR(10))
164
+ AS
165
+ BEGIN
166
+ RETURN SELECT STATUS FROM T WHERE ID IN (1, 2, 3) AND TYPE = 'IN';
167
+ END
168
+ `;
169
+ const result = (0, index_1.extractFunctionParameters)(ddl);
170
+ (0, vitest_1.expect)(result).toEqual([
171
+ { type: 'inputParameter', name: 'IV_STATUS' },
172
+ { type: 'inputParameter', name: 'IV_ID' }
173
+ ]);
174
+ });
175
+ });
176
+ // ---------------------------------------------------------------------------
177
+ // AC-7 Block comment exclusion
178
+ // ---------------------------------------------------------------------------
179
+ (0, vitest_1.describe)('AC-7: block comment exclusion', () => {
180
+ (0, vitest_1.it)('does not extract a parameter wrapped in /* … */', () => {
181
+ const ddl = `
182
+ FUNCTION F (
183
+ IN IV_ACTIVE BOOLEAN
184
+ /* , IN IV_OLD NVARCHAR(10) */
185
+ ) RETURNS INTEGER AS BEGIN END
186
+ `;
187
+ const names = inputs(ddl);
188
+ (0, vitest_1.expect)(names).not.toContain('IV_OLD');
189
+ (0, vitest_1.expect)(names).toContain('IV_ACTIVE');
190
+ });
191
+ (0, vitest_1.it)('handles block comment spanning multiple lines', () => {
192
+ const ddl = `
193
+ FUNCTION F (
194
+ /*
195
+ * IN IV_COMMENTED INTEGER,
196
+ */
197
+ IN IV_REAL NVARCHAR(10)
198
+ ) RETURNS INTEGER AS BEGIN END
199
+ `;
200
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_REAL']);
201
+ (0, vitest_1.expect)(inputs(ddl)).not.toContain('IV_COMMENTED');
202
+ });
203
+ });
204
+ // ---------------------------------------------------------------------------
205
+ // AC-8 Line comment exclusion
206
+ // ---------------------------------------------------------------------------
207
+ (0, vitest_1.describe)('AC-8: line comment exclusion', () => {
208
+ (0, vitest_1.it)('does not extract a parameter on a -- comment line', () => {
209
+ const ddl = `
210
+ FUNCTION F (
211
+ IN IV_ID INTEGER
212
+ -- , IN IV_OLD NVARCHAR(10)
213
+ ) RETURNS INTEGER AS BEGIN END
214
+ `;
215
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_ID']);
216
+ (0, vitest_1.expect)(inputs(ddl)).not.toContain('IV_OLD');
217
+ });
218
+ });
219
+ // ---------------------------------------------------------------------------
220
+ // AC-9 Quoted identifier normalisation
221
+ // ---------------------------------------------------------------------------
222
+ (0, vitest_1.describe)('AC-9: quoted identifier normalisation', () => {
223
+ (0, vitest_1.it)('strips double-quotes from a quoted parameter name', () => {
224
+ const ddl = `FUNCTION F (IN "IV_CUSTOMER_ID" NVARCHAR(10)) RETURNS INTEGER AS BEGIN END`;
225
+ (0, vitest_1.expect)((0, index_1.extractFunctionParameters)(ddl)).toContainEqual({
226
+ type: 'inputParameter',
227
+ name: 'IV_CUSTOMER_ID'
228
+ });
229
+ });
230
+ (0, vitest_1.it)('handles mixed quoted and unquoted parameters', () => {
231
+ const ddl = `
232
+ FUNCTION F (
233
+ IN "IV_QUOTED" NVARCHAR(10),
234
+ IN IV_UNQUOTED INTEGER
235
+ ) RETURNS INTEGER AS BEGIN END
236
+ `;
237
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_QUOTED', 'IV_UNQUOTED']);
238
+ });
239
+ });
240
+ // ---------------------------------------------------------------------------
241
+ // AC-10 Schema-qualified function name
242
+ // ---------------------------------------------------------------------------
243
+ (0, vitest_1.describe)('AC-10: schema-qualified function name', () => {
244
+ (0, vitest_1.it)('parses schema-qualified name without error', () => {
245
+ const ddl = `
246
+ FUNCTION "MY_SCHEMA"."MY_FUNCTION" (IN IV_ID INTEGER)
247
+ RETURNS INTEGER AS BEGIN END
248
+ `;
249
+ (0, vitest_1.expect)(() => (0, index_1.extractFunctionParameters)(ddl)).not.toThrow();
250
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_ID']);
251
+ });
252
+ (0, vitest_1.it)('parses unquoted schema-qualified name without error', () => {
253
+ const ddl = `FUNCTION MY_SCHEMA.MY_FUNC (IN IV_X INTEGER) RETURNS INTEGER AS BEGIN END`;
254
+ (0, vitest_1.expect)(() => (0, index_1.extractFunctionParameters)(ddl)).not.toThrow();
255
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_X']);
256
+ });
257
+ });
258
+ // ---------------------------------------------------------------------------
259
+ // AC-11 Function options are ignored
260
+ // ---------------------------------------------------------------------------
261
+ (0, vitest_1.describe)('AC-11: function option clauses do not affect extraction', () => {
262
+ (0, vitest_1.it)('handles LANGUAGE SQLSCRIPT SQL SECURITY INVOKER', () => {
263
+ const ddl = `
264
+ FUNCTION F (IN IV_ID INTEGER)
265
+ RETURNS INTEGER
266
+ LANGUAGE SQLSCRIPT
267
+ SQL SECURITY INVOKER
268
+ AS BEGIN END
269
+ `;
270
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_ID']);
271
+ });
272
+ (0, vitest_1.it)('handles SQL SECURITY DEFINER WITH ENCRYPTION', () => {
273
+ const ddl = `
274
+ FUNCTION F (IN IV_X NVARCHAR(10))
275
+ RETURNS NVARCHAR(10)
276
+ SQL SECURITY DEFINER
277
+ WITH ENCRYPTION
278
+ AS BEGIN END
279
+ `;
280
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_X']);
281
+ });
282
+ (0, vitest_1.it)('handles DEFAULT SCHEMA option', () => {
283
+ const ddl = `
284
+ FUNCTION F (IN IV_X INTEGER)
285
+ RETURNS INTEGER
286
+ DEFAULT SCHEMA MY_SCHEMA
287
+ AS BEGIN END
288
+ `;
289
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_X']);
290
+ });
291
+ (0, vitest_1.it)('handles all options combined', () => {
292
+ const ddl = `
293
+ CREATE FUNCTION F (IN IV_A INTEGER, IN IV_B NVARCHAR(10))
294
+ RETURNS TABLE (RES_COL NVARCHAR(10))
295
+ LANGUAGE SQLSCRIPT
296
+ SQL SECURITY DEFINER
297
+ DEFAULT SCHEMA MY_SCHEMA
298
+ WITH ENCRYPTION
299
+ AS BEGIN END
300
+ `;
301
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_A', 'IV_B']);
302
+ });
303
+ });
304
+ // ---------------------------------------------------------------------------
305
+ // AC-12 Empty parameter list
306
+ // ---------------------------------------------------------------------------
307
+ (0, vitest_1.describe)('AC-12: empty parameter list', () => {
308
+ (0, vitest_1.it)('returns empty array for a function with no parameters', () => {
309
+ const ddl = `FUNCTION F () RETURNS INTEGER AS BEGIN END`;
310
+ (0, vitest_1.expect)((0, index_1.extractFunctionParameters)(ddl)).toEqual([]);
311
+ });
312
+ (0, vitest_1.it)('returns empty array for a table function with no parameters', () => {
313
+ const ddl = `FUNCTION F () RETURNS TABLE (COL1 INTEGER) AS BEGIN END`;
314
+ (0, vitest_1.expect)((0, index_1.extractFunctionParameters)(ddl)).toEqual([]);
315
+ });
316
+ });
317
+ // ---------------------------------------------------------------------------
318
+ // AC-13 CREATE keyword optional
319
+ // ---------------------------------------------------------------------------
320
+ (0, vitest_1.describe)('AC-13: CREATE keyword optional', () => {
321
+ (0, vitest_1.it)('extracts identically with and without CREATE keyword', () => {
322
+ const withCreate = `CREATE FUNCTION F (IN IV_ID INTEGER) RETURNS INTEGER AS BEGIN END`;
323
+ const withoutCreate = `FUNCTION F (IN IV_ID INTEGER) RETURNS INTEGER AS BEGIN END`;
324
+ (0, vitest_1.expect)((0, index_1.extractFunctionParameters)(withCreate)).toEqual((0, index_1.extractFunctionParameters)(withoutCreate));
325
+ });
326
+ });
327
+ // ---------------------------------------------------------------------------
328
+ // AC-14 Graceful error on unparseable file
329
+ // ---------------------------------------------------------------------------
330
+ (0, vitest_1.describe)('AC-14: graceful error handling', () => {
331
+ (0, vitest_1.it)('does not throw on invalid syntax', () => {
332
+ const ddl = `FUNCTION ??? GARBAGE SYNTAX`;
333
+ (0, vitest_1.expect)(() => (0, index_1.extractFunctionParameters)(ddl)).not.toThrow();
334
+ });
335
+ (0, vitest_1.it)('returns an array (possibly empty) on invalid syntax', () => {
336
+ const ddl = `FUNCTION ??? GARBAGE SYNTAX`;
337
+ (0, vitest_1.expect)(Array.isArray((0, index_1.extractFunctionParameters)(ddl))).toBe(true);
338
+ });
339
+ (0, vitest_1.it)('does not throw on an empty string', () => {
340
+ (0, vitest_1.expect)(() => (0, index_1.extractFunctionParameters)('')).not.toThrow();
341
+ (0, vitest_1.expect)(Array.isArray((0, index_1.extractFunctionParameters)(''))).toBe(true);
342
+ });
343
+ });
344
+ // ---------------------------------------------------------------------------
345
+ // AC-15 CRLF line endings
346
+ // ---------------------------------------------------------------------------
347
+ (0, vitest_1.describe)('AC-15: CRLF line ending support', () => {
348
+ (0, vitest_1.it)('correctly extracts parameters from a file with CRLF line endings', () => {
349
+ const ddl = 'FUNCTION F (\r\n IN IV_ID INTEGER,\r\n IN IV_NAME NVARCHAR(100)\r\n) RETURNS INTEGER AS BEGIN END';
350
+ (0, vitest_1.expect)(inputs(ddl)).toEqual(['IV_ID', 'IV_NAME']);
351
+ });
352
+ });
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractFunctionParameters = extractFunctionParameters;
4
+ const lexer_1 = require("./lexer");
5
+ const parser_1 = require("./parser");
6
+ const visitor_1 = require("./visitor");
7
+ /**
8
+ * Extract parameter names from the content of a `.hdbfunction` DDL file.
9
+ *
10
+ * Uses a Chevrotain lexer and CstParser. The parser recognises the function
11
+ * header (parameter list, RETURNS clause, and options) and consumes the entire
12
+ * function body (AS BEGIN … END) as an opaque block, ensuring that SQL keywords
13
+ * inside the body never contaminate the extraction result.
14
+ *
15
+ * HANA functions accept only IN parameters. Every parameter produces an
16
+ * { type: 'inputParameter', name } entry. No 'outputParameter' entries are
17
+ * ever produced. RETURNS clause types and RETURNS TABLE column names are
18
+ * parsed structurally but not extracted.
19
+ *
20
+ * Handles block/line comments, quoted and unquoted identifiers, schema-qualified
21
+ * function names, TABLE-type IN parameters, RETURNS TABLE definitions, and all
22
+ * standard function options. Gracefully returns partial results on invalid input
23
+ * — does not throw.
24
+ *
25
+ * @param fileContent - Raw UTF-8 file content (LF or CRLF).
26
+ * @returns Array of ExtractedSubject with type 'inputParameter' only.
27
+ */
28
+ function extractFunctionParameters(fileContent) {
29
+ const lexResult = lexer_1.HdbFunctionLexer.tokenize(fileContent);
30
+ // Feed the token stream to the singleton parser.
31
+ parser_1.hdbFunctionParser.input = lexResult.tokens;
32
+ const cst = parser_1.hdbFunctionParser.createFunctionStatement();
33
+ // Lex/parse errors are intentionally not re-thrown — the CST visitor
34
+ // will extract whatever parameters could be parsed from the partial tree.
35
+ // Guard against a completely unrecoverable parse (cst may be undefined).
36
+ if (!cst) {
37
+ return [];
38
+ }
39
+ const visitor = new visitor_1.HdbFunctionParameterVisitor();
40
+ visitor.visit(cst);
41
+ return visitor.parameters;
42
+ }