patram 0.3.0 → 0.5.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.
@@ -1,6 +1,5 @@
1
- /** @import * as $k$$l$parse$j$claims$k$types$k$ts from './parse-claims.types.ts'; */
2
1
  /**
3
- * @import { ParseClaimsInput, ParseSourceFileResult } from './parse-claims.types.ts';
2
+ * @import { ParseClaimsInput, ParseSourceFileResult, PatramClaim } from './parse-claims.types.ts';
4
3
  */
5
4
 
6
5
  import { getFileExtension } from './claim-helpers.js';
@@ -51,7 +50,7 @@ export function parseSourceFile(parse_input) {
51
50
  * Parse a file into neutral Patram claims.
52
51
  *
53
52
  * @param {ParseClaimsInput} parse_input
54
- * @returns {$k$$l$parse$j$claims$k$types$k$ts.PatramClaim[]}
53
+ * @returns {PatramClaim[]}
55
54
  */
56
55
  export function parseClaims(parse_input) {
57
56
  return parseSourceFile(parse_input).claims;
@@ -1,6 +1,6 @@
1
- /** @import * as $k$$l$load$j$patram$j$config$k$types$k$ts from './load-patram-config.types.ts'; */
2
1
  /**
3
- * @import { ParseClaimsInput, ParseSourceFileResult, PatramClaim, PatramClaimFields } from './parse-claims.types.ts';
2
+ * @import { PatramDiagnostic } from './load-patram-config.types.ts';
3
+ * @import { ParseClaimsInput, ParseSourceFileResult, PatramClaimFields } from './parse-claims.types.ts';
4
4
  */
5
5
 
6
6
  import {
@@ -266,7 +266,7 @@ function compareClaimEntries(left_entry, right_entry) {
266
266
  /**
267
267
  * @param {string} file_path
268
268
  * @param {{ activation_column: number | null, activation_line: number | null }} jsdoc_block
269
- * @returns {$k$$l$load$j$patram$j$config$k$types$k$ts.PatramDiagnostic}
269
+ * @returns {PatramDiagnostic}
270
270
  */
271
271
  function createMultiplePatramBlocksDiagnostic(file_path, jsdoc_block) {
272
272
  return {
@@ -6,7 +6,7 @@
6
6
  * ParsedAggregateComparison,
7
7
  * ParsedAggregateName,
8
8
  * ParsedAggregateTerm,
9
- * ParsedClause,
9
+ * ParsedExpression,
10
10
  * ParsedFieldName,
11
11
  * ParsedTerm,
12
12
  * ParsedTraversalTerm,
@@ -17,6 +17,16 @@
17
17
  * @typedef {{ index: number, where_clause: string }} ParserState
18
18
  */
19
19
 
20
+ /**
21
+ * @typedef {{
22
+ * expression: ParsedExpression,
23
+ * success: true,
24
+ * } | {
25
+ * diagnostic: PatramDiagnostic,
26
+ * success: false,
27
+ * }} ParseExpressionResult
28
+ */
29
+
20
30
  /**
21
31
  * @typedef {{
22
32
  * success: true,
@@ -28,7 +38,7 @@
28
38
  */
29
39
 
30
40
  /**
31
- * Parse one where clause into structured clauses.
41
+ * Parse one where clause into a structured boolean expression.
32
42
  *
33
43
  * @param {string} where_clause
34
44
  * @returns {ParseWhereClauseResult}
@@ -43,10 +53,10 @@ export function parseWhereClause(where_clause) {
43
53
  return fail(1, 'Query must not be empty.');
44
54
  }
45
55
 
46
- const clauses_result = parseClauses(parser_state, null);
56
+ const expression_result = parseExpression(parser_state, null);
47
57
 
48
- if (!clauses_result.success) {
49
- return clauses_result;
58
+ if (!expression_result.success) {
59
+ return expression_result;
50
60
  }
51
61
 
52
62
  skipWhitespace(parser_state);
@@ -56,7 +66,7 @@ export function parseWhereClause(where_clause) {
56
66
  }
57
67
 
58
68
  return {
59
- clauses: clauses_result.clauses,
69
+ expression: expression_result.expression,
60
70
  success: true,
61
71
  };
62
72
  }
@@ -64,12 +74,29 @@ export function parseWhereClause(where_clause) {
64
74
  /**
65
75
  * @param {ParserState} parser_state
66
76
  * @param {')' | null} stop_character
67
- * @returns {ParseWhereClauseResult}
77
+ * @returns {ParseExpressionResult}
68
78
  */
69
- function parseClauses(parser_state, stop_character) {
70
- /** @type {ParsedClause[]} */
71
- const clauses = [];
72
- let is_first_clause = true;
79
+ function parseExpression(parser_state, stop_character) {
80
+ return parseOrExpression(parser_state, stop_character);
81
+ }
82
+
83
+ /**
84
+ * @param {ParserState} parser_state
85
+ * @param {')' | null} stop_character
86
+ * @returns {ParseExpressionResult}
87
+ */
88
+ function parseOrExpression(parser_state, stop_character) {
89
+ const first_expression_result = parseAndExpression(
90
+ parser_state,
91
+ stop_character,
92
+ );
93
+
94
+ if (!first_expression_result.success) {
95
+ return first_expression_result;
96
+ }
97
+
98
+ /** @type {ParsedExpression[]} */
99
+ const expressions = [first_expression_result.expression];
73
100
 
74
101
  while (true) {
75
102
  skipWhitespace(parser_state);
@@ -78,68 +105,185 @@ function parseClauses(parser_state, stop_character) {
78
105
  currentCharacter(parser_state) === stop_character ||
79
106
  isAtEnd(parser_state)
80
107
  ) {
81
- return is_first_clause
82
- ? fail(parser_state.where_clause.length + 1, 'Expected a query term.')
83
- : { clauses, success: true };
108
+ return collapseBooleanExpression('or', expressions);
84
109
  }
85
110
 
86
- if (!is_first_clause) {
87
- if (!consumeKeyword(parser_state, 'and')) {
88
- return failToken(parser_state);
89
- }
90
-
91
- skipWhitespace(parser_state);
92
-
93
- if (
94
- currentCharacter(parser_state) === stop_character ||
95
- isAtEnd(parser_state)
96
- ) {
97
- return fail(
98
- parser_state.where_clause.length + 1,
99
- 'Expected a query term.',
100
- );
101
- }
111
+ if (!consumeKeyword(parser_state, 'or')) {
112
+ return collapseBooleanExpression('or', expressions);
102
113
  }
103
114
 
104
- const clause_result = parseClause(parser_state);
115
+ skipWhitespace(parser_state);
105
116
 
106
- if (!clause_result.success) {
107
- return clause_result;
117
+ if (
118
+ currentCharacter(parser_state) === stop_character ||
119
+ isAtEnd(parser_state)
120
+ ) {
121
+ return failExpectedTerm(parser_state);
108
122
  }
109
123
 
110
- clauses.push(clause_result.clause);
111
- is_first_clause = false;
124
+ const next_expression_result = parseAndExpression(
125
+ parser_state,
126
+ stop_character,
127
+ );
128
+
129
+ if (!next_expression_result.success) {
130
+ return next_expression_result;
131
+ }
132
+
133
+ expressions.push(next_expression_result.expression);
112
134
  }
113
135
  }
114
136
 
115
137
  /**
116
138
  * @param {ParserState} parser_state
117
- * @returns {{ clause: ParsedClause, success: true } | { diagnostic: PatramDiagnostic, success: false }}
139
+ * @param {')' | null} stop_character
140
+ * @returns {ParseExpressionResult}
118
141
  */
119
- function parseClause(parser_state) {
120
- const clause_start = parser_state.index;
121
- const is_negated = consumeKeyword(parser_state, 'not');
142
+ function parseAndExpression(parser_state, stop_character) {
143
+ const first_expression_result = parseUnaryExpression(
144
+ parser_state,
145
+ stop_character,
146
+ );
147
+
148
+ if (!first_expression_result.success) {
149
+ return first_expression_result;
150
+ }
151
+
152
+ /** @type {ParsedExpression[]} */
153
+ const expressions = [first_expression_result.expression];
154
+
155
+ while (true) {
156
+ skipWhitespace(parser_state);
157
+
158
+ if (
159
+ currentCharacter(parser_state) === stop_character ||
160
+ isAtEnd(parser_state)
161
+ ) {
162
+ return collapseBooleanExpression('and', expressions);
163
+ }
164
+
165
+ if (!consumeKeyword(parser_state, 'and')) {
166
+ return collapseBooleanExpression('and', expressions);
167
+ }
122
168
 
123
- if (is_negated && !consumeRequiredWhitespace(parser_state)) {
169
+ skipWhitespace(parser_state);
170
+
171
+ if (
172
+ currentCharacter(parser_state) === stop_character ||
173
+ isAtEnd(parser_state)
174
+ ) {
175
+ return failExpectedTerm(parser_state);
176
+ }
177
+
178
+ const next_expression_result = parseUnaryExpression(
179
+ parser_state,
180
+ stop_character,
181
+ );
182
+
183
+ if (!next_expression_result.success) {
184
+ return next_expression_result;
185
+ }
186
+
187
+ expressions.push(next_expression_result.expression);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * @param {ParserState} parser_state
193
+ * @param {')' | null} stop_character
194
+ * @returns {ParseExpressionResult}
195
+ */
196
+ function parseUnaryExpression(parser_state, stop_character) {
197
+ skipWhitespace(parser_state);
198
+ const start_index = parser_state.index;
199
+
200
+ if (!consumeKeyword(parser_state, 'not')) {
201
+ return parsePrimaryExpression(parser_state, stop_character);
202
+ }
203
+
204
+ const whitespace_length = skipWhitespace(parser_state);
205
+
206
+ if (whitespace_length === 0 && currentCharacter(parser_state) !== '(') {
124
207
  return fail(
125
- clause_start + 1,
126
- `Unsupported query token "${readToken(parser_state, clause_start)}".`,
208
+ start_index + 1,
209
+ `Unsupported query token "${readToken(parser_state, start_index)}".`,
127
210
  );
128
211
  }
129
212
 
213
+ if (
214
+ currentCharacter(parser_state) === stop_character ||
215
+ isAtEnd(parser_state)
216
+ ) {
217
+ return failExpectedTerm(parser_state);
218
+ }
219
+
220
+ const expression_result = parseUnaryExpression(parser_state, stop_character);
221
+
222
+ if (!expression_result.success) {
223
+ return expression_result;
224
+ }
225
+
226
+ return successExpression({
227
+ expression: expression_result.expression,
228
+ kind: 'not',
229
+ });
230
+ }
231
+
232
+ /**
233
+ * @param {ParserState} parser_state
234
+ * @param {')' | null} stop_character
235
+ * @returns {ParseExpressionResult}
236
+ */
237
+ function parsePrimaryExpression(parser_state, stop_character) {
238
+ skipWhitespace(parser_state);
239
+
240
+ if (
241
+ currentCharacter(parser_state) === stop_character ||
242
+ isAtEnd(parser_state)
243
+ ) {
244
+ return failExpectedTerm(parser_state);
245
+ }
246
+
247
+ if (consumeOperator(parser_state, '(')) {
248
+ const expression_result = parseExpression(parser_state, ')');
249
+
250
+ if (!expression_result.success) {
251
+ return expression_result;
252
+ }
253
+
254
+ if (!consumeOperator(parser_state, ')')) {
255
+ return failToken(parser_state);
256
+ }
257
+
258
+ return expression_result;
259
+ }
260
+
130
261
  const term_result = parseTerm(parser_state);
131
262
 
132
263
  if (!term_result.success) {
133
264
  return term_result;
134
265
  }
135
266
 
136
- return {
137
- clause: {
138
- is_negated,
139
- term: term_result.term,
140
- },
141
- success: true,
142
- };
267
+ return successExpression({
268
+ kind: 'term',
269
+ term: term_result.term,
270
+ });
271
+ }
272
+
273
+ /**
274
+ * @param {'and' | 'or'} kind
275
+ * @param {ParsedExpression[]} expressions
276
+ * @returns {ParseExpressionResult}
277
+ */
278
+ function collapseBooleanExpression(kind, expressions) {
279
+ if (expressions.length === 1) {
280
+ return successExpression(expressions[0]);
281
+ }
282
+
283
+ return successExpression({
284
+ expressions,
285
+ kind,
286
+ });
143
287
  }
144
288
 
145
289
  /**
@@ -188,10 +332,10 @@ function parseAggregate(parser_state) {
188
332
  }
189
333
 
190
334
  skipWhitespace(parser_state);
191
- const clauses_result = parseClauses(parser_state, ')');
335
+ const expression_result = parseExpression(parser_state, ')');
192
336
 
193
- if (!clauses_result.success) {
194
- return clauses_result;
337
+ if (!expression_result.success) {
338
+ return expression_result;
195
339
  }
196
340
 
197
341
  if (!consumeOperator(parser_state, ')')) {
@@ -202,7 +346,7 @@ function parseAggregate(parser_state) {
202
346
  parser_state,
203
347
  aggregate_name,
204
348
  traversal_result.traversal,
205
- clauses_result.clauses,
349
+ expression_result.expression,
206
350
  );
207
351
  }
208
352
 
@@ -225,20 +369,25 @@ function parseAtomicTerm(parser_state) {
225
369
  * @param {ParserState} parser_state
226
370
  * @param {ParsedAggregateName} aggregate_name
227
371
  * @param {ParsedTraversalTerm} traversal
228
- * @param {ParsedClause[]} clauses
372
+ * @param {ParsedExpression} expression
229
373
  * @returns {ParseTermResult}
230
374
  */
231
- function createAggregateTerm(parser_state, aggregate_name, traversal, clauses) {
375
+ function createAggregateTerm(
376
+ parser_state,
377
+ aggregate_name,
378
+ traversal,
379
+ expression,
380
+ ) {
232
381
  /** @type {ParsedAggregateTerm} */
233
382
  const aggregate_term = {
234
383
  aggregate_name,
235
- clauses,
384
+ expression,
236
385
  kind: 'aggregate',
237
386
  traversal,
238
387
  };
239
388
 
240
389
  if (aggregate_name !== 'count') {
241
- return success(aggregate_term);
390
+ return successTerm(aggregate_term);
242
391
  }
243
392
 
244
393
  const count_tail = parseCountTail(parser_state);
@@ -247,7 +396,7 @@ function createAggregateTerm(parser_state, aggregate_name, traversal, clauses) {
247
396
  return failToken(parser_state);
248
397
  }
249
398
 
250
- return success({
399
+ return successTerm({
251
400
  ...aggregate_term,
252
401
  comparison: count_tail.comparison,
253
402
  value: count_tail.value,
@@ -262,6 +411,7 @@ function createAggregateTerm(parser_state, aggregate_name, traversal, clauses) {
262
411
  */
263
412
  function parseFieldSet(parser_state, start_index, field_name) {
264
413
  const operator_start_index = parser_state.index;
414
+
265
415
  if (!consumeRequiredWhitespace(parser_state)) {
266
416
  return null;
267
417
  }
@@ -280,7 +430,7 @@ function parseFieldSet(parser_state, start_index, field_name) {
280
430
  const values = parseList(parser_state);
281
431
 
282
432
  return values
283
- ? success({
433
+ ? successTerm({
284
434
  column: start_index + 1,
285
435
  field_name,
286
436
  kind: 'field_set',
@@ -348,7 +498,7 @@ function parseOperatorTerm(parser_state, start_index, field_or_relation_name) {
348
498
  }
349
499
 
350
500
  if (consumeOperator(parser_state, ':*')) {
351
- return success({
501
+ return successTerm({
352
502
  column: start_index + 1,
353
503
  kind: 'relation',
354
504
  relation_name: field_or_relation_name,
@@ -435,7 +585,7 @@ function parsePrefixTerm(parser_state, start_index, field_name) {
435
585
  const value = parseBareValue(parser_state);
436
586
 
437
587
  return value
438
- ? success({
588
+ ? successTerm({
439
589
  column: start_index + 1,
440
590
  field_name,
441
591
  kind: 'field',
@@ -460,7 +610,7 @@ function parseContainsTerm(parser_state, start_index, field_name) {
460
610
  const value = parseBareValue(parser_state);
461
611
 
462
612
  return value
463
- ? success({
613
+ ? successTerm({
464
614
  column: start_index + 1,
465
615
  field_name,
466
616
  kind: 'field',
@@ -493,7 +643,7 @@ function parseEqualityTerm(parser_state, start_index, field_or_relation_name) {
493
643
  field_or_relation_name === 'title' ||
494
644
  !value.includes(':')
495
645
  ) {
496
- return success({
646
+ return successTerm({
497
647
  column: start_index + 1,
498
648
  field_name: field_or_relation_name,
499
649
  kind: 'field',
@@ -502,7 +652,7 @@ function parseEqualityTerm(parser_state, start_index, field_or_relation_name) {
502
652
  });
503
653
  }
504
654
 
505
- return success({
655
+ return successTerm({
506
656
  column: start_index + 1,
507
657
  kind: 'relation_target',
508
658
  relation_name: field_or_relation_name,
@@ -530,7 +680,7 @@ function parseFieldComparisonTerm(parser_state, start_index, field_name) {
530
680
  return failToken(parser_state);
531
681
  }
532
682
 
533
- return success({
683
+ return successTerm({
534
684
  column: start_index + 1,
535
685
  field_name,
536
686
  kind: 'field',
@@ -725,11 +875,19 @@ function isAtEnd(parser_state) {
725
875
  return parser_state.index >= parser_state.where_clause.length;
726
876
  }
727
877
 
878
+ /**
879
+ * @param {ParsedExpression} expression
880
+ * @returns {ParseExpressionResult}
881
+ */
882
+ function successExpression(expression) {
883
+ return { expression, success: true };
884
+ }
885
+
728
886
  /**
729
887
  * @param {ParsedTerm} term
730
888
  * @returns {ParseTermResult}
731
889
  */
732
- function success(term) {
890
+ function successTerm(term) {
733
891
  return { success: true, term };
734
892
  }
735
893
 
@@ -744,6 +902,19 @@ function failToken(parser_state) {
744
902
  );
745
903
  }
746
904
 
905
+ /**
906
+ * @param {ParserState} parser_state
907
+ * @returns {{ diagnostic: PatramDiagnostic, success: false }}
908
+ */
909
+ function failExpectedTerm(parser_state) {
910
+ return fail(
911
+ isAtEnd(parser_state)
912
+ ? parser_state.where_clause.length + 1
913
+ : parser_state.index + 1,
914
+ 'Expected a query term.',
915
+ );
916
+ }
917
+
747
918
  /**
748
919
  * @param {number} column
749
920
  * @param {string} message
@@ -42,13 +42,28 @@ export type ParsedAggregateName = 'any' | 'count' | 'none';
42
42
 
43
43
  export interface ParsedAggregateTerm {
44
44
  aggregate_name: ParsedAggregateName;
45
- clauses: ParsedClause[];
46
45
  comparison?: ParsedAggregateComparison;
46
+ expression: ParsedExpression;
47
47
  kind: 'aggregate';
48
48
  traversal: ParsedTraversalTerm;
49
49
  value?: number;
50
50
  }
51
51
 
52
+ export interface ParsedTermExpression {
53
+ kind: 'term';
54
+ term: ParsedTerm;
55
+ }
56
+
57
+ export interface ParsedNotExpression {
58
+ expression: ParsedExpression;
59
+ kind: 'not';
60
+ }
61
+
62
+ export interface ParsedBooleanExpression {
63
+ expressions: ParsedExpression[];
64
+ kind: 'and' | 'or';
65
+ }
66
+
52
67
  export type ParsedTerm =
53
68
  | ParsedAggregateTerm
54
69
  | ParsedFieldSetTerm
@@ -56,14 +71,14 @@ export type ParsedTerm =
56
71
  | ParsedRelationTargetTerm
57
72
  | ParsedRelationTerm;
58
73
 
59
- export interface ParsedClause {
60
- is_negated: boolean;
61
- term: ParsedTerm;
62
- }
74
+ export type ParsedExpression =
75
+ | ParsedBooleanExpression
76
+ | ParsedNotExpression
77
+ | ParsedTermExpression;
63
78
 
64
79
  export type ParseWhereClauseResult =
65
80
  | {
66
- clauses: ParsedClause[];
81
+ expression: ParsedExpression;
67
82
  success: true;
68
83
  }
69
84
  | {
package/lib/patram-cli.js CHANGED
@@ -9,6 +9,7 @@ import { checkGraph } from './check-graph.js';
9
9
  import {
10
10
  shouldPageCommandOutput,
11
11
  writeCommandOutput,
12
+ writeRenderedCommandOutput,
12
13
  } from './command-output.js';
13
14
  import { discoverFields } from './discover-fields.js';
14
15
  import { listRepoFiles } from './list-source-files.js';
@@ -257,9 +258,20 @@ async function runFieldsCommand(parsed_command, io_context) {
257
258
  no_color: process.env.NO_COLOR !== undefined,
258
259
  term: process.env.TERM,
259
260
  });
260
- const discovery_result = await discoverFields(process.cwd());
261
+ const load_result = await loadPatramConfig(process.cwd());
262
+ const defined_field_names =
263
+ load_result.diagnostics.length === 0
264
+ ? collectDefinedDiscoveryNames(load_result.config)
265
+ : new Set();
266
+ const discovery_result = await discoverFields(process.cwd(), {
267
+ defined_field_names,
268
+ });
261
269
 
262
- io_context.stdout.write(renderFieldDiscovery(discovery_result, output_mode));
270
+ await writeRenderedCommandOutput(
271
+ io_context,
272
+ parsed_command,
273
+ renderFieldDiscovery(discovery_result, output_mode),
274
+ );
263
275
 
264
276
  return 0;
265
277
  }
@@ -395,6 +407,7 @@ async function runShowCommand(parsed_command, io_context) {
395
407
  parsed_command,
396
408
  createShowOutputView(show_output.value, {
397
409
  derived_summary_evaluator,
410
+ document_node_ids: project_graph_result.graph.document_node_ids,
398
411
  graph_nodes: project_graph_result.graph.nodes,
399
412
  repo_config: project_graph_result.config,
400
413
  }),
@@ -494,3 +507,22 @@ function createQueryExecutionOptions(parsed_command, use_pager) {
494
507
  function formatDiagnostic(diagnostic) {
495
508
  return `${diagnostic.path}:${diagnostic.line}:${diagnostic.column} ${diagnostic.level} ${diagnostic.code} ${diagnostic.message}\n`;
496
509
  }
510
+
511
+ /**
512
+ * @param {import('./load-patram-config.types.ts').PatramRepoConfig | null} repo_config
513
+ * @returns {Set<string>}
514
+ */
515
+ function collectDefinedDiscoveryNames(repo_config) {
516
+ /** @type {Set<string>} */
517
+ const defined_field_names = new Set();
518
+
519
+ for (const field_name of Object.keys(repo_config?.fields ?? {})) {
520
+ defined_field_names.add(field_name);
521
+ }
522
+
523
+ for (const relation_name of Object.keys(repo_config?.relations ?? {})) {
524
+ defined_field_names.add(relation_name);
525
+ }
526
+
527
+ return defined_field_names;
528
+ }