patram 0.2.0 → 0.3.0
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/lib/build-graph-identity.js +20 -19
- package/lib/build-graph.js +369 -16
- package/lib/build-graph.types.ts +5 -2
- package/lib/check-directive-metadata.js +516 -0
- package/lib/check-directive-value.js +282 -0
- package/lib/check-graph.js +24 -5
- package/lib/cli-help-metadata.js +44 -16
- package/lib/derived-summary.js +10 -8
- package/lib/directive-diagnostics.js +38 -0
- package/lib/directive-type-rules.js +133 -0
- package/lib/discover-fields.js +427 -0
- package/lib/discover-fields.types.ts +52 -0
- package/lib/format-node-header.js +9 -7
- package/lib/format-output-metadata.js +15 -23
- package/lib/layout-stored-queries.js +6 -60
- package/lib/load-patram-config.js +433 -96
- package/lib/load-patram-config.types.ts +98 -3
- package/lib/load-project-graph.js +4 -1
- package/lib/output-view.types.ts +14 -6
- package/lib/parse-cli-arguments.types.ts +1 -1
- package/lib/parse-where-clause.js +117 -51
- package/lib/parse-where-clause.types.ts +4 -2
- package/lib/patram-cli.js +36 -4
- package/lib/patram-config.js +31 -31
- package/lib/patram-config.types.ts +10 -4
- package/lib/query-graph.js +241 -22
- package/lib/query-inspection.js +285 -10
- package/lib/render-field-discovery.js +148 -0
- package/lib/render-json-output.js +21 -22
- package/lib/render-output-view.js +240 -19
- package/lib/render-plain-output.js +1 -1
- package/lib/render-rich-output.js +1 -1
- package/lib/resolve-patram-graph-config.js +15 -9
- package/lib/show-document.js +51 -7
- package/package.json +5 -5
package/lib/patram-config.js
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const CLASS_NAME_SCHEMA = z.string().min(1);
|
|
9
9
|
const RELATION_NAME_SCHEMA = z.string().min(1);
|
|
10
10
|
const CLAIM_TYPE_SCHEMA = z.string().min(1);
|
|
11
11
|
const KEY_SOURCE_SCHEMA = z.enum(['path', 'value']);
|
|
12
12
|
const TARGET_SCHEMA = z.enum(['path', 'value']);
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const class_definition_schema = z
|
|
15
15
|
.object({
|
|
16
16
|
builtin: z.boolean().optional(),
|
|
17
17
|
label: z.string().min(1).optional(),
|
|
@@ -21,16 +21,16 @@ const kind_definition_schema = z
|
|
|
21
21
|
const relation_definition_schema = z
|
|
22
22
|
.object({
|
|
23
23
|
builtin: z.boolean().optional(),
|
|
24
|
-
from: z.array(
|
|
25
|
-
to: z.array(
|
|
24
|
+
from: z.array(CLASS_NAME_SCHEMA).min(1),
|
|
25
|
+
to: z.array(CLASS_NAME_SCHEMA).min(1),
|
|
26
26
|
})
|
|
27
27
|
.strict();
|
|
28
28
|
|
|
29
29
|
const mapping_node_schema = z
|
|
30
30
|
.object({
|
|
31
|
+
class: CLASS_NAME_SCHEMA,
|
|
31
32
|
field: z.string().min(1),
|
|
32
33
|
key: KEY_SOURCE_SCHEMA.optional(),
|
|
33
|
-
kind: KIND_NAME_SCHEMA,
|
|
34
34
|
})
|
|
35
35
|
.strict();
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ const mapping_emit_schema = z
|
|
|
38
38
|
.object({
|
|
39
39
|
relation: RELATION_NAME_SCHEMA,
|
|
40
40
|
target: TARGET_SCHEMA,
|
|
41
|
-
|
|
41
|
+
target_class: CLASS_NAME_SCHEMA,
|
|
42
42
|
})
|
|
43
43
|
.strict();
|
|
44
44
|
|
|
@@ -53,7 +53,7 @@ const mapping_definition_schema = z
|
|
|
53
53
|
export const patramConfigSchema = z
|
|
54
54
|
.object({
|
|
55
55
|
$schema: z.url().optional(),
|
|
56
|
-
|
|
56
|
+
classes: z.record(CLASS_NAME_SCHEMA, class_definition_schema),
|
|
57
57
|
mappings: z.record(CLAIM_TYPE_SCHEMA, mapping_definition_schema),
|
|
58
58
|
relations: z.record(RELATION_NAME_SCHEMA, relation_definition_schema),
|
|
59
59
|
})
|
|
@@ -90,8 +90,8 @@ function validateMappingDefinition(mapping_definition, refinement_context) {
|
|
|
90
90
|
* @param {RefinementCtx} refinement_context
|
|
91
91
|
*/
|
|
92
92
|
function validatePatramConfigReferences(config_json, refinement_context) {
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
validateRelationClasses(config_json, refinement_context);
|
|
94
|
+
validateMappingClasses(config_json, refinement_context);
|
|
95
95
|
validateMappingRelations(config_json, refinement_context);
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -99,19 +99,19 @@ function validatePatramConfigReferences(config_json, refinement_context) {
|
|
|
99
99
|
* @param {PatramConfig} config_json
|
|
100
100
|
* @param {RefinementCtx} refinement_context
|
|
101
101
|
*/
|
|
102
|
-
function
|
|
102
|
+
function validateRelationClasses(config_json, refinement_context) {
|
|
103
103
|
for (const [relation_name, relation_definition] of Object.entries(
|
|
104
104
|
config_json.relations,
|
|
105
105
|
)) {
|
|
106
|
-
|
|
106
|
+
validateReferencedClasses(
|
|
107
107
|
relation_definition.from,
|
|
108
|
-
config_json.
|
|
108
|
+
config_json.classes,
|
|
109
109
|
['relations', relation_name, 'from'],
|
|
110
110
|
refinement_context,
|
|
111
111
|
);
|
|
112
|
-
|
|
112
|
+
validateReferencedClasses(
|
|
113
113
|
relation_definition.to,
|
|
114
|
-
config_json.
|
|
114
|
+
config_json.classes,
|
|
115
115
|
['relations', relation_name, 'to'],
|
|
116
116
|
refinement_context,
|
|
117
117
|
);
|
|
@@ -122,24 +122,24 @@ function validateRelationKinds(config_json, refinement_context) {
|
|
|
122
122
|
* @param {PatramConfig} config_json
|
|
123
123
|
* @param {RefinementCtx} refinement_context
|
|
124
124
|
*/
|
|
125
|
-
function
|
|
125
|
+
function validateMappingClasses(config_json, refinement_context) {
|
|
126
126
|
for (const [mapping_name, mapping_definition] of Object.entries(
|
|
127
127
|
config_json.mappings,
|
|
128
128
|
)) {
|
|
129
129
|
if (mapping_definition.emit) {
|
|
130
|
-
|
|
131
|
-
[mapping_definition.emit.
|
|
132
|
-
config_json.
|
|
133
|
-
['mappings', mapping_name, 'emit', '
|
|
130
|
+
validateReferencedClasses(
|
|
131
|
+
[mapping_definition.emit.target_class],
|
|
132
|
+
config_json.classes,
|
|
133
|
+
['mappings', mapping_name, 'emit', 'target_class'],
|
|
134
134
|
refinement_context,
|
|
135
135
|
);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
if (mapping_definition.node) {
|
|
139
|
-
|
|
140
|
-
[mapping_definition.node.
|
|
141
|
-
config_json.
|
|
142
|
-
['mappings', mapping_name, 'node', '
|
|
139
|
+
validateReferencedClasses(
|
|
140
|
+
[mapping_definition.node.class],
|
|
141
|
+
config_json.classes,
|
|
142
|
+
['mappings', mapping_name, 'node', 'class'],
|
|
143
143
|
refinement_context,
|
|
144
144
|
);
|
|
145
145
|
}
|
|
@@ -171,25 +171,25 @@ function validateMappingRelations(config_json, refinement_context) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
|
-
* @param {string[]}
|
|
175
|
-
* @param {Record<string, unknown>}
|
|
174
|
+
* @param {string[]} referenced_classes
|
|
175
|
+
* @param {Record<string, unknown>} known_classes
|
|
176
176
|
* @param {(string | number)[]} issue_path
|
|
177
177
|
* @param {RefinementCtx} refinement_context
|
|
178
178
|
*/
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
function validateReferencedClasses(
|
|
180
|
+
referenced_classes,
|
|
181
|
+
known_classes,
|
|
182
182
|
issue_path,
|
|
183
183
|
refinement_context,
|
|
184
184
|
) {
|
|
185
|
-
for (const
|
|
186
|
-
if (
|
|
185
|
+
for (const referenced_class of referenced_classes) {
|
|
186
|
+
if (known_classes[referenced_class]) {
|
|
187
187
|
continue;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
refinement_context.addIssue({
|
|
191
191
|
code: 'custom',
|
|
192
|
-
message: `Unknown
|
|
192
|
+
message: `Unknown class "${referenced_class}".`,
|
|
193
193
|
path: issue_path,
|
|
194
194
|
});
|
|
195
195
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface ClassDefinition {
|
|
2
2
|
builtin?: boolean;
|
|
3
3
|
label?: string;
|
|
4
4
|
}
|
|
@@ -10,15 +10,15 @@ export interface RelationDefinition {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface MappingNodeDefinition {
|
|
13
|
+
class: string;
|
|
13
14
|
field: string;
|
|
14
15
|
key?: 'path' | 'value';
|
|
15
|
-
kind: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export interface MappingEmitDefinition {
|
|
19
19
|
relation: string;
|
|
20
20
|
target: 'path' | 'value';
|
|
21
|
-
|
|
21
|
+
target_class: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface MappingDefinition {
|
|
@@ -28,7 +28,13 @@ export interface MappingDefinition {
|
|
|
28
28
|
|
|
29
29
|
export interface PatramConfig {
|
|
30
30
|
$schema?: string;
|
|
31
|
-
|
|
31
|
+
classes: Record<string, ClassDefinition>;
|
|
32
|
+
class_schemas?: Record<string, ClassSchemaConfig>;
|
|
33
|
+
fields?: Record<string, MetadataFieldConfig>;
|
|
32
34
|
mappings: Record<string, MappingDefinition>;
|
|
33
35
|
relations: Record<string, RelationDefinition>;
|
|
34
36
|
}
|
|
37
|
+
import type {
|
|
38
|
+
ClassSchemaConfig,
|
|
39
|
+
MetadataFieldConfig,
|
|
40
|
+
} from './load-patram-config.types.ts';
|
package/lib/query-graph.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
2
|
/**
|
|
3
3
|
* @import { BuildGraphResult, GraphNode } from './build-graph.types.ts';
|
|
4
|
-
* @import { PatramDiagnostic } from './load-patram-config.types.ts';
|
|
4
|
+
* @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
|
|
5
5
|
* @import {
|
|
6
6
|
* ParsedAggregateTerm,
|
|
7
7
|
* ParsedClause,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { parseWhereClause } from './parse-where-clause.js';
|
|
17
|
+
import { getQuerySemanticDiagnostics } from './query-inspection.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Query graph filtering.
|
|
@@ -54,10 +55,16 @@ export const DEFAULT_QUERY_LIMIT = 25;
|
|
|
54
55
|
*
|
|
55
56
|
* @param {BuildGraphResult} graph
|
|
56
57
|
* @param {string} where_clause
|
|
58
|
+
* @param {PatramRepoConfig | { limit?: number, offset?: number }=} repo_config_or_pagination
|
|
57
59
|
* @param {{ limit?: number, offset?: number }=} pagination_options
|
|
58
60
|
* @returns {{ diagnostics: PatramDiagnostic[], nodes: GraphNode[], total_count: number }}
|
|
59
61
|
*/
|
|
60
|
-
export function queryGraph(
|
|
62
|
+
export function queryGraph(
|
|
63
|
+
graph,
|
|
64
|
+
where_clause,
|
|
65
|
+
repo_config_or_pagination = {},
|
|
66
|
+
pagination_options = {},
|
|
67
|
+
) {
|
|
61
68
|
const parse_result = parseWhereClause(where_clause);
|
|
62
69
|
|
|
63
70
|
if (!parse_result.success) {
|
|
@@ -68,6 +75,25 @@ export function queryGraph(graph, where_clause, pagination_options = {}) {
|
|
|
68
75
|
};
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
const { pagination_options: resolved_pagination_options, repo_config } =
|
|
79
|
+
resolveQueryGraphOptions(repo_config_or_pagination, pagination_options);
|
|
80
|
+
|
|
81
|
+
if (repo_config) {
|
|
82
|
+
const diagnostics = getQuerySemanticDiagnostics(
|
|
83
|
+
repo_config,
|
|
84
|
+
{ kind: 'ad_hoc' },
|
|
85
|
+
parse_result.clauses,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (diagnostics.length > 0) {
|
|
89
|
+
return {
|
|
90
|
+
diagnostics,
|
|
91
|
+
nodes: [],
|
|
92
|
+
total_count: 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
const evaluation_context = {
|
|
72
98
|
nodes: graph.nodes,
|
|
73
99
|
relation_indexes: createRelationIndexes(graph.edges),
|
|
@@ -76,7 +102,10 @@ export function queryGraph(graph, where_clause, pagination_options = {}) {
|
|
|
76
102
|
const matching_nodes = graph_nodes.filter((graph_node) =>
|
|
77
103
|
matchesClauses(graph_node, parse_result.clauses, evaluation_context),
|
|
78
104
|
);
|
|
79
|
-
const paginated_nodes = paginateNodes(
|
|
105
|
+
const paginated_nodes = paginateNodes(
|
|
106
|
+
matching_nodes,
|
|
107
|
+
resolved_pagination_options,
|
|
108
|
+
);
|
|
80
109
|
|
|
81
110
|
return {
|
|
82
111
|
diagnostics: [],
|
|
@@ -176,29 +205,48 @@ function matchesAggregateTerm(graph_node, term, evaluation_context) {
|
|
|
176
205
|
* @returns {boolean}
|
|
177
206
|
*/
|
|
178
207
|
function matchesFieldTerm(graph_node, term) {
|
|
179
|
-
const
|
|
208
|
+
const field_values = getGraphNodeFieldValues(graph_node, term.field_name);
|
|
180
209
|
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
term_key === 'kind=' ||
|
|
184
|
-
term_key === 'path=' ||
|
|
185
|
-
term_key === 'status='
|
|
186
|
-
) {
|
|
187
|
-
return graph_node[term.field_name] === term.value;
|
|
210
|
+
if (term.operator === '=') {
|
|
211
|
+
return field_values.some((field_value) => field_value === term.value);
|
|
188
212
|
}
|
|
189
213
|
|
|
190
|
-
if (
|
|
191
|
-
return
|
|
214
|
+
if (term.operator === '!=') {
|
|
215
|
+
return field_values.every((field_value) => field_value !== term.value);
|
|
192
216
|
}
|
|
193
217
|
|
|
194
|
-
if (
|
|
195
|
-
return
|
|
218
|
+
if (term.operator === '^=') {
|
|
219
|
+
return field_values.some((field_value) =>
|
|
220
|
+
field_value.startsWith(term.value),
|
|
221
|
+
);
|
|
196
222
|
}
|
|
197
223
|
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
224
|
+
if (term.operator === '~') {
|
|
225
|
+
const term_value = term.value.toLocaleLowerCase('en');
|
|
226
|
+
|
|
227
|
+
return field_values.some((field_value) =>
|
|
228
|
+
field_value.toLocaleLowerCase('en').includes(term_value),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (term.operator === '<' || term.operator === '<=') {
|
|
233
|
+
return field_values.some((field_value) =>
|
|
234
|
+
compareFieldValues(
|
|
235
|
+
field_value,
|
|
236
|
+
/** @type {'<' | '<='} */ (term.operator),
|
|
237
|
+
term.value,
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (term.operator === '>' || term.operator === '>=') {
|
|
243
|
+
return field_values.some((field_value) =>
|
|
244
|
+
compareFieldValues(
|
|
245
|
+
field_value,
|
|
246
|
+
/** @type {'>' | '>='} */ (term.operator),
|
|
247
|
+
term.value,
|
|
248
|
+
),
|
|
249
|
+
);
|
|
202
250
|
}
|
|
203
251
|
|
|
204
252
|
throw new Error('Unsupported parsed where clause.');
|
|
@@ -210,8 +258,10 @@ function matchesFieldTerm(graph_node, term) {
|
|
|
210
258
|
* @returns {boolean}
|
|
211
259
|
*/
|
|
212
260
|
function matchesFieldSetTerm(graph_node, term) {
|
|
213
|
-
const
|
|
214
|
-
const is_member =
|
|
261
|
+
const field_values = getGraphNodeFieldValues(graph_node, term.field_name);
|
|
262
|
+
const is_member = field_values.some((field_value) =>
|
|
263
|
+
term.values.includes(field_value),
|
|
264
|
+
);
|
|
215
265
|
|
|
216
266
|
return term.operator === 'in' ? is_member : !is_member;
|
|
217
267
|
}
|
|
@@ -352,7 +402,10 @@ function createDirectionalRelationIndex(graph_edges, source_key, target_key) {
|
|
|
352
402
|
* @returns {number}
|
|
353
403
|
*/
|
|
354
404
|
function compareGraphNodes(left_node, right_node) {
|
|
355
|
-
return left_node.id.localeCompare(
|
|
405
|
+
return (left_node.$id ?? left_node.id).localeCompare(
|
|
406
|
+
right_node.$id ?? right_node.id,
|
|
407
|
+
'en',
|
|
408
|
+
);
|
|
356
409
|
}
|
|
357
410
|
|
|
358
411
|
/**
|
|
@@ -366,3 +419,169 @@ function paginateNodes(matching_nodes, pagination_options) {
|
|
|
366
419
|
|
|
367
420
|
return matching_nodes.slice(offset, offset + limit);
|
|
368
421
|
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @param {GraphNode} graph_node
|
|
425
|
+
* @param {string} field_name
|
|
426
|
+
* @returns {string[]}
|
|
427
|
+
*/
|
|
428
|
+
function getGraphNodeFieldValues(graph_node, field_name) {
|
|
429
|
+
const structural_value = getStructuralFieldValue(graph_node, field_name);
|
|
430
|
+
|
|
431
|
+
if (structural_value !== undefined) {
|
|
432
|
+
return [structural_value];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (field_name === '$path' || field_name === 'title') {
|
|
436
|
+
return [];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return normalizeFieldValues(graph_node[field_name]);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* @param {GraphNode} graph_node
|
|
444
|
+
* @param {string} field_name
|
|
445
|
+
* @returns {string | undefined}
|
|
446
|
+
*/
|
|
447
|
+
function getStructuralFieldValue(graph_node, field_name) {
|
|
448
|
+
if (field_name === '$id') {
|
|
449
|
+
return graph_node.$id ?? graph_node.id;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (field_name === '$class') {
|
|
453
|
+
return graph_node.$class ?? graph_node.kind;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (field_name === '$path') {
|
|
457
|
+
return graph_node.$path ?? graph_node.path;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (field_name === 'title') {
|
|
461
|
+
return graph_node.title;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* @param {unknown} field_value
|
|
467
|
+
* @returns {string[]}
|
|
468
|
+
*/
|
|
469
|
+
function normalizeFieldValues(field_value) {
|
|
470
|
+
if (Array.isArray(field_value)) {
|
|
471
|
+
return field_value.flatMap(getScalarFieldValue);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return getScalarFieldValue(field_value);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* @param {unknown} field_value
|
|
479
|
+
* @returns {string[]}
|
|
480
|
+
*/
|
|
481
|
+
function getScalarFieldValue(field_value) {
|
|
482
|
+
if (
|
|
483
|
+
typeof field_value === 'string' ||
|
|
484
|
+
typeof field_value === 'number' ||
|
|
485
|
+
typeof field_value === 'boolean'
|
|
486
|
+
) {
|
|
487
|
+
return [String(field_value)];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return [];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* @param {PatramRepoConfig | { limit?: number, offset?: number }} repo_config_or_pagination
|
|
495
|
+
* @param {{ limit?: number, offset?: number }} pagination_options
|
|
496
|
+
* @returns {{ pagination_options: { limit?: number, offset?: number }, repo_config: PatramRepoConfig | null }}
|
|
497
|
+
*/
|
|
498
|
+
function resolveQueryGraphOptions(
|
|
499
|
+
repo_config_or_pagination,
|
|
500
|
+
pagination_options,
|
|
501
|
+
) {
|
|
502
|
+
if (isRepoConfig(repo_config_or_pagination)) {
|
|
503
|
+
return {
|
|
504
|
+
pagination_options,
|
|
505
|
+
repo_config: repo_config_or_pagination,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
pagination_options: repo_config_or_pagination,
|
|
511
|
+
repo_config: null,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* @param {PatramRepoConfig | { limit?: number, offset?: number }} value
|
|
517
|
+
* @returns {value is PatramRepoConfig}
|
|
518
|
+
*/
|
|
519
|
+
function isRepoConfig(value) {
|
|
520
|
+
return 'include' in value || 'queries' in value;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* @param {string} left_value
|
|
525
|
+
* @param {'<' | '<=' | '>' | '>='} comparison
|
|
526
|
+
* @param {string} right_value
|
|
527
|
+
* @returns {boolean}
|
|
528
|
+
*/
|
|
529
|
+
function compareFieldValues(left_value, comparison, right_value) {
|
|
530
|
+
const numeric_comparison = compareNumericFieldValues(
|
|
531
|
+
left_value,
|
|
532
|
+
comparison,
|
|
533
|
+
right_value,
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
if (numeric_comparison !== null) {
|
|
537
|
+
return numeric_comparison;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return compareComparableValues(
|
|
541
|
+
left_value.localeCompare(right_value, 'en'),
|
|
542
|
+
comparison,
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* @param {string} left_value
|
|
548
|
+
* @param {'<' | '<=' | '>' | '>='} comparison
|
|
549
|
+
* @param {string} right_value
|
|
550
|
+
* @returns {boolean | null}
|
|
551
|
+
*/
|
|
552
|
+
function compareNumericFieldValues(left_value, comparison, right_value) {
|
|
553
|
+
const left_number = Number(left_value);
|
|
554
|
+
const right_number = Number(right_value);
|
|
555
|
+
|
|
556
|
+
if (
|
|
557
|
+
!Number.isFinite(left_number) ||
|
|
558
|
+
!Number.isFinite(right_number) ||
|
|
559
|
+
left_value.trim().length === 0 ||
|
|
560
|
+
right_value.trim().length === 0
|
|
561
|
+
) {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return compareComparableValues(left_number - right_number, comparison);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* @param {number} comparison_result
|
|
570
|
+
* @param {'<' | '<=' | '>' | '>='} comparison
|
|
571
|
+
* @returns {boolean}
|
|
572
|
+
*/
|
|
573
|
+
function compareComparableValues(comparison_result, comparison) {
|
|
574
|
+
if (comparison === '<') {
|
|
575
|
+
return comparison_result < 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (comparison === '<=') {
|
|
579
|
+
return comparison_result <= 0;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (comparison === '>') {
|
|
583
|
+
return comparison_result > 0;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return comparison_result >= 0;
|
|
587
|
+
}
|