@checkdigit/eslint-plugin 7.18.0-PR.143-b47c → 7.18.0-PR.143-cf9d

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.
@@ -42,21 +42,18 @@ function getFunctionName(node: unknown): string | undefined {
42
42
  return firstName === undefined ? undefined : firstName.value.toLowerCase();
43
43
  }
44
44
 
45
- function getColumnName(node: unknown): string | undefined {
45
+ function getColumnRef(node: unknown): ColumnRefItem | undefined {
46
46
  const col = rec(node);
47
- if (col?.['type'] !== 'column_ref') {
48
- return undefined;
49
- }
50
- const column = (node as ColumnRefItem).column;
47
+ return col?.['type'] === 'column_ref' ? (node as ColumnRefItem) : undefined;
48
+ }
49
+
50
+ function getColumnName(node: unknown): string | undefined {
51
+ const column = getColumnRef(node)?.column;
51
52
  return typeof column === 'string' ? column.toLowerCase() : undefined;
52
53
  }
53
54
 
54
55
  function getColumnTable(node: unknown): string | null | undefined {
55
- const col = rec(node);
56
- if (col?.['type'] !== 'column_ref') {
57
- return undefined;
58
- }
59
- return (node as ColumnRefItem).table;
56
+ return getColumnRef(node)?.table;
60
57
  }
61
58
 
62
59
  function getStringValue(node: unknown): string | undefined {
@@ -72,75 +69,49 @@ function getNumberValue(node: unknown): number | undefined {
72
69
  return val?.['type'] === 'number' && typeof val['value'] === 'number' ? val['value'] : undefined;
73
70
  }
74
71
 
72
+ // Returns the function args when the node is split(url, '/') or split_part(url, '/'), else undefined.
73
+ function getSplitUrlFunctionArgs(node: unknown, functionName: 'split' | 'split_part'): unknown[] | undefined {
74
+ if (getFunctionName(node) !== functionName) {
75
+ return undefined;
76
+ }
77
+ const args = (rec(node)?.['args'] as { value?: unknown[] } | undefined)?.value;
78
+ if (!Array.isArray(args) || args.length < 2) {
79
+ return undefined;
80
+ }
81
+ if (getColumnName(args[0]) !== 'url' || getStringValue(args[1]) !== '/') {
82
+ return undefined;
83
+ }
84
+ return args;
85
+ }
86
+
75
87
  // Matches: split(url, '/')[N] — a Function node carrying an array_index extension
76
88
  interface SplitUrlIndexed extends SqlFunction {
77
89
  array_index: { brackets: true; index: { type: string; value: unknown } }[];
78
90
  }
79
91
  function isSplitUrlIndexed(node: unknown): node is SplitUrlIndexed {
80
- const fn = rec(node);
81
- if (fn?.['type'] !== 'function') {
82
- return false;
83
- }
84
- if (getFunctionName(node) !== 'split') {
85
- return false;
86
- }
87
- const args = (fn['args'] as { value?: unknown[] } | undefined)?.value;
88
- if (!Array.isArray(args) || args.length < 2) {
92
+ if (getSplitUrlFunctionArgs(node, 'split') === undefined) {
89
93
  return false;
90
94
  }
91
- if (getColumnName(args[0]) !== 'url') {
92
- return false;
93
- }
94
- if (getStringValue(args[1]) !== '/') {
95
- return false;
96
- }
97
- return Array.isArray(fn['array_index']) && (fn['array_index'] as unknown[]).length > 0;
95
+ const fn = rec(node);
96
+ return Array.isArray(fn?.['array_index']) && (fn['array_index'] as unknown[]).length > 0;
98
97
  }
99
98
 
100
99
  // Matches: split_part(url, '/', N) — Presto-style, index in args[2] (1-based)
101
100
  function isSplitPartUrl(node: unknown): node is SqlFunction {
102
- const fn = rec(node);
103
- if (fn?.['type'] !== 'function') {
104
- return false;
105
- }
106
- if (getFunctionName(node) !== 'split_part') {
107
- return false;
108
- }
109
- const args = (fn['args'] as { value?: unknown[] } | undefined)?.value;
110
- if (!Array.isArray(args) || args[2] === undefined) {
111
- return false;
112
- }
113
- if (getColumnName(args[0]) !== 'url') {
114
- return false;
115
- }
116
- return getStringValue(args[1]) === '/' && getNumberValue(args[2]) !== undefined;
101
+ const args = getSplitUrlFunctionArgs(node, 'split_part');
102
+ return getNumberValue(args?.[2]) !== undefined;
117
103
  }
118
104
 
119
105
  // Matches: cardinality(split(url, '/'))
120
106
  function isCardinalitySplitUrl(node: unknown): node is SqlFunction {
121
- const fn = rec(node);
122
- if (fn?.['type'] !== 'function') {
123
- return false;
124
- }
125
107
  if (getFunctionName(node) !== 'cardinality') {
126
108
  return false;
127
109
  }
128
- const outerArgs = (fn['args'] as { value?: unknown[] } | undefined)?.value;
110
+ const outerArgs = (rec(node)?.['args'] as { value?: unknown[] } | undefined)?.value;
129
111
  if (!Array.isArray(outerArgs) || outerArgs.length === 0) {
130
112
  return false;
131
113
  }
132
- const innerFn = rec(outerArgs[0]);
133
- if (innerFn?.['type'] !== 'function') {
134
- return false;
135
- }
136
- if (getFunctionName(outerArgs[0]) !== 'split') {
137
- return false;
138
- }
139
- const innerArgs = (innerFn['args'] as { value?: unknown[] } | undefined)?.value;
140
- if (!Array.isArray(innerArgs) || innerArgs.length < 2) {
141
- return false;
142
- }
143
- return getColumnName(innerArgs[0]) === 'url' && getStringValue(innerArgs[1]) === '/';
114
+ return getSplitUrlFunctionArgs(outerArgs[0], 'split') !== undefined;
144
115
  }
145
116
 
146
117
  // Returns the table qualifier of the left-hand side of a matchable binary condition.
@@ -151,13 +122,9 @@ function getConditionTableQualifier(left: unknown): string | null | undefined {
151
122
  return getColumnTable(left);
152
123
  }
153
124
 
154
- if (isSplitUrlIndexed(left)) {
155
- const args = (rec(left)?.['args'] as { value?: unknown[] } | undefined)?.value;
156
- return getColumnTable(args?.[0]) ?? null;
157
- }
158
- if (isSplitPartUrl(left)) {
159
- const args = (rec(left)?.['args'] as { value?: unknown[] } | undefined)?.value;
160
- return getColumnTable(args?.[0]) ?? null;
125
+ const splitArgs = getSplitUrlFunctionArgs(left, 'split') ?? getSplitUrlFunctionArgs(left, 'split_part');
126
+ if (splitArgs !== undefined) {
127
+ return getColumnTable(splitArgs[0]) ?? null;
161
128
  }
162
129
  if (isCardinalitySplitUrl(left)) {
163
130
  const outerArgs = (rec(left)?.['args'] as { value?: unknown[] } | undefined)?.value;
@@ -170,6 +137,16 @@ function getConditionTableQualifier(left: unknown): string | null | undefined {
170
137
 
171
138
  // --- Predicate builders ---
172
139
 
140
+ // Builds a predicate that checks whether the Nth path segment (1-based) equals a fixed value.
141
+ // Path parameters (`:param`) are treated as wildcards and always match.
142
+ function buildPathSegmentPredicate(index: number, value: string): OperationPredicate {
143
+ return (path) => {
144
+ const part = path.split('/')[index - 1];
145
+ log('checking path segment', { path, index, part, value });
146
+ return part?.startsWith(':') === true ? true : part === value;
147
+ };
148
+ }
149
+
173
150
  // tableAlias: the alias (or null if none) of the FROM-clause item we are currently matching.
174
151
  // Conditions that explicitly reference a different alias are skipped (treated as ALWAYS_TRUE).
175
152
  function buildLeafPredicate(node: Binary, tableAlias: string | null): OperationPredicate | undefined {
@@ -206,27 +183,16 @@ function buildLeafPredicate(node: Binary, tableAlias: string | null): OperationP
206
183
  const index = left.array_index[0]?.index.value;
207
184
  const value = getStringValue(right);
208
185
  if (typeof index === 'number' && value !== undefined) {
209
- return (path) => {
210
- const parts = path.split('/');
211
- const part = parts[index - 1]; // athena index is 1-based
212
- log(`checking path part`, { path, index, part, value });
213
- return part?.startsWith(':') === true ? true : part === value;
214
- };
186
+ return buildPathSegmentPredicate(index, value);
215
187
  }
216
188
  }
217
189
 
218
190
  // split_part(url, '/', N) = 'value'
219
191
  if (isSplitPartUrl(left)) {
220
- const args = (rec(left)?.['args'] as { value?: unknown[] } | undefined)?.value;
221
- const index = getNumberValue(args?.[2]);
192
+ const index = getNumberValue(getSplitUrlFunctionArgs(left, 'split_part')?.[2]);
222
193
  const value = getStringValue(right);
223
194
  if (index !== undefined && value !== undefined) {
224
- return (path) => {
225
- const parts = path.split('/');
226
- const part = parts[index - 1]; // 1-based
227
- log('checking split_part path part', { path, index, part, value });
228
- return part?.startsWith(':') === true ? true : part === value;
229
- };
195
+ return buildPathSegmentPredicate(index, value);
230
196
  }
231
197
  }
232
198