@malloydata/malloy 0.0.386 → 0.0.388
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/dist/api/foundation/config.d.ts +29 -14
- package/dist/api/foundation/config.js +37 -15
- package/dist/api/foundation/config_lookup.js +15 -1
- package/dist/api/foundation/runtime.d.ts +21 -10
- package/dist/api/foundation/runtime.js +44 -13
- package/dist/connection/base_connection.d.ts +1 -0
- package/dist/connection/base_connection.js +1 -0
- package/dist/connection/registry.d.ts +5 -2
- package/dist/connection/registry.js +7 -0
- package/dist/connection/types.d.ts +14 -0
- package/dist/lang/prettify/block-body.js +18 -5
- package/dist/lang/prettify/formatter.js +5 -0
- package/dist/lang/prettify/import-select.d.ts +3 -0
- package/dist/lang/prettify/import-select.js +88 -0
- package/dist/lang/prettify/index.js +24 -0
- package/dist/lang/prettify/leaf.js +27 -0
- package/dist/lang/prettify/rules.d.ts +1 -1
- package/dist/lang/prettify/rules.js +8 -0
- package/dist/lang/prettify/sections.js +140 -21
- package/dist/lang/prettify/tokens.js +25 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -133,27 +133,42 @@ export declare class MalloyConfig {
|
|
|
133
133
|
*/
|
|
134
134
|
wrapConnections(wrapper: (base: LookupConnection<Connection>) => LookupConnection<Connection>): void;
|
|
135
135
|
/**
|
|
136
|
-
* Notify every connection this config has handed out that
|
|
137
|
-
*
|
|
136
|
+
* Notify every connection this config has handed out that the host is
|
|
137
|
+
* done with it, applying the requested disposition to its backend
|
|
138
|
+
* resources. Two policies:
|
|
138
139
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
140
|
+
* - `'close'` (default) — destructive. Calls `Connection.close()` on each
|
|
141
|
+
* cached entry and drops the cache. Subsequent operations on those
|
|
142
|
+
* connection objects may fail. Use this at real shutdown: process
|
|
143
|
+
* exit, extension deactivate, config-file change.
|
|
144
|
+
*
|
|
145
|
+
* - `'idle'` — reversible. Calls `Connection.idle()` on each cached entry
|
|
146
|
+
* without dropping the cache. The same Connection objects are reused
|
|
147
|
+
* on next lookup; schema cache and other in-process state survive.
|
|
148
|
+
* The next operation transparently reattaches any backend resources
|
|
149
|
+
* that were released. Use this between operations in long-lived hosts
|
|
150
|
+
* (VSCode, MCP servers, anything that builds Runtimes per request) to
|
|
151
|
+
* release file locks / pooled sockets while the host is otherwise
|
|
152
|
+
* idle.
|
|
153
|
+
*
|
|
154
|
+
* Most callers should use `Runtime.shutdown(...)` instead — the expected
|
|
155
|
+
* contract is one MalloyConfig per Runtime, and the runtime is the
|
|
156
|
+
* natural handle for lifecycle. This method exists because Runtime
|
|
142
157
|
* forwards to it, and for the rare case of a MalloyConfig constructed
|
|
143
158
|
* without an accompanying Runtime.
|
|
144
159
|
*
|
|
145
160
|
* MalloyConfig does not own any connection resources itself — pools,
|
|
146
161
|
* sockets, file handles, and in-process databases all live inside the
|
|
147
162
|
* individual Connection objects. What the managed lookup owns is a cache
|
|
148
|
-
* of `name → Connection` populated lazily as callers request
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
*
|
|
163
|
+
* of `name → Connection` populated lazily as callers request
|
|
164
|
+
* connections. Connections that were never looked up were never
|
|
165
|
+
* constructed and have nothing to release; they are skipped. Wrapping
|
|
166
|
+
* lookups installed via `wrapConnections()` do not interfere — the
|
|
167
|
+
* managed lookup under the wrap is the one holding the cache.
|
|
168
|
+
*/
|
|
169
|
+
shutdown(connections?: 'close' | 'idle'): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* @deprecated Use `shutdown('close')` instead.
|
|
157
172
|
*/
|
|
158
173
|
releaseConnections(): Promise<void>;
|
|
159
174
|
/**
|
|
@@ -216,30 +216,52 @@ class MalloyConfig {
|
|
|
216
216
|
this._connections = wrapper(this._connections);
|
|
217
217
|
}
|
|
218
218
|
/**
|
|
219
|
-
* Notify every connection this config has handed out that
|
|
220
|
-
*
|
|
219
|
+
* Notify every connection this config has handed out that the host is
|
|
220
|
+
* done with it, applying the requested disposition to its backend
|
|
221
|
+
* resources. Two policies:
|
|
221
222
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
223
|
+
* - `'close'` (default) — destructive. Calls `Connection.close()` on each
|
|
224
|
+
* cached entry and drops the cache. Subsequent operations on those
|
|
225
|
+
* connection objects may fail. Use this at real shutdown: process
|
|
226
|
+
* exit, extension deactivate, config-file change.
|
|
227
|
+
*
|
|
228
|
+
* - `'idle'` — reversible. Calls `Connection.idle()` on each cached entry
|
|
229
|
+
* without dropping the cache. The same Connection objects are reused
|
|
230
|
+
* on next lookup; schema cache and other in-process state survive.
|
|
231
|
+
* The next operation transparently reattaches any backend resources
|
|
232
|
+
* that were released. Use this between operations in long-lived hosts
|
|
233
|
+
* (VSCode, MCP servers, anything that builds Runtimes per request) to
|
|
234
|
+
* release file locks / pooled sockets while the host is otherwise
|
|
235
|
+
* idle.
|
|
236
|
+
*
|
|
237
|
+
* Most callers should use `Runtime.shutdown(...)` instead — the expected
|
|
238
|
+
* contract is one MalloyConfig per Runtime, and the runtime is the
|
|
239
|
+
* natural handle for lifecycle. This method exists because Runtime
|
|
225
240
|
* forwards to it, and for the rare case of a MalloyConfig constructed
|
|
226
241
|
* without an accompanying Runtime.
|
|
227
242
|
*
|
|
228
243
|
* MalloyConfig does not own any connection resources itself — pools,
|
|
229
244
|
* sockets, file handles, and in-process databases all live inside the
|
|
230
245
|
* individual Connection objects. What the managed lookup owns is a cache
|
|
231
|
-
* of `name → Connection` populated lazily as callers request
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
246
|
+
* of `name → Connection` populated lazily as callers request
|
|
247
|
+
* connections. Connections that were never looked up were never
|
|
248
|
+
* constructed and have nothing to release; they are skipped. Wrapping
|
|
249
|
+
* lookups installed via `wrapConnections()` do not interfere — the
|
|
250
|
+
* managed lookup under the wrap is the one holding the cache.
|
|
251
|
+
*/
|
|
252
|
+
async shutdown(connections = 'close') {
|
|
253
|
+
if (connections === 'idle') {
|
|
254
|
+
await this._managedLookup.idle();
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
await this._managedLookup.close();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* @deprecated Use `shutdown('close')` instead.
|
|
240
262
|
*/
|
|
241
263
|
async releaseConnections() {
|
|
242
|
-
await this.
|
|
264
|
+
await this.shutdown('close');
|
|
243
265
|
}
|
|
244
266
|
/**
|
|
245
267
|
* Query a value from the overlays used to resolve this config.
|
|
@@ -83,6 +83,13 @@ function buildManagedLookup(compiledConnections, overlays, log) {
|
|
|
83
83
|
await conn.close();
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
|
+
async idle() {
|
|
87
|
+
// Cache is preserved — same Connection objects are reused so that
|
|
88
|
+
// schema cache and other in-process state survive the idle.
|
|
89
|
+
for (const conn of cache.values()) {
|
|
90
|
+
await conn.idle();
|
|
91
|
+
}
|
|
92
|
+
},
|
|
86
93
|
};
|
|
87
94
|
}
|
|
88
95
|
/**
|
|
@@ -91,7 +98,14 @@ function buildManagedLookup(compiledConnections, overlays, log) {
|
|
|
91
98
|
* gets handed to the factory.
|
|
92
99
|
*/
|
|
93
100
|
async function resolveCompiledEntry(entry, overlays, log) {
|
|
94
|
-
const resolved =
|
|
101
|
+
const resolved = await resolveNode(entry, overlays, log);
|
|
102
|
+
// resolveNode returns `unknown`. The compileConnections pipeline
|
|
103
|
+
// guarantees every connection entry is an object dict with `is` set to
|
|
104
|
+
// a literal string, so this should always pass — but a structured
|
|
105
|
+
// throw beats a downstream NPE if a compiler bug ever sneaks through.
|
|
106
|
+
if (!(0, registry_1.isConnectionConfigEntry)(resolved)) {
|
|
107
|
+
throw new Error('Connection entry did not resolve to a valid {is: string, ...} dict');
|
|
108
|
+
}
|
|
95
109
|
await applyPropertyDefaults(resolved, overlays);
|
|
96
110
|
return resolved;
|
|
97
111
|
}
|
|
@@ -126,16 +126,27 @@ export declare class Runtime {
|
|
|
126
126
|
get virtualMap(): VirtualMap | undefined;
|
|
127
127
|
set virtualMap(map: VirtualMap | undefined);
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
129
|
+
* Tell this runtime's connections what to do with their backend
|
|
130
|
+
* resources now that the host is done with this Runtime. Two policies:
|
|
131
|
+
*
|
|
132
|
+
* - `'close'` (default) — destructive shutdown. Connections release
|
|
133
|
+
* resources and become unusable. Use at real shutdown: process exit,
|
|
134
|
+
* extension deactivate, config-file change.
|
|
135
|
+
*
|
|
136
|
+
* - `'idle'` — reversible release. Connections release expensive
|
|
137
|
+
* resources (DuckDB file locks, socket pools) but stay logically
|
|
138
|
+
* valid. The same Connection objects are reused on next lookup;
|
|
139
|
+
* schema cache and other in-process state survive. Use between
|
|
140
|
+
* operations in long-lived hosts (a VSCode extension, an MCP server,
|
|
141
|
+
* any host that builds Runtimes per request) so that other writers
|
|
142
|
+
* can claim resources during idle gaps.
|
|
143
|
+
*
|
|
144
|
+
* A no-op for runtimes constructed without a MalloyConfig — in that
|
|
145
|
+
* case the caller owns the connections they passed in.
|
|
146
|
+
*/
|
|
147
|
+
shutdown(connections?: 'close' | 'idle'): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* @deprecated Use `shutdown('close')` instead.
|
|
139
150
|
*/
|
|
140
151
|
releaseConnections(): Promise<void>;
|
|
141
152
|
/**
|
|
@@ -160,7 +160,11 @@ class Runtime {
|
|
|
160
160
|
return undefined;
|
|
161
161
|
}
|
|
162
162
|
try {
|
|
163
|
-
|
|
163
|
+
const parsed = JSON.parse(text);
|
|
164
|
+
if (!isBuildManifestShape(parsed)) {
|
|
165
|
+
throw new Error('manifest is not an object with an "entries" map');
|
|
166
|
+
}
|
|
167
|
+
return parsed;
|
|
164
168
|
}
|
|
165
169
|
catch (e) {
|
|
166
170
|
// File was present but couldn't be parsed. Return an empty
|
|
@@ -193,20 +197,33 @@ class Runtime {
|
|
|
193
197
|
this._virtualMap = map;
|
|
194
198
|
}
|
|
195
199
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
200
|
+
* Tell this runtime's connections what to do with their backend
|
|
201
|
+
* resources now that the host is done with this Runtime. Two policies:
|
|
202
|
+
*
|
|
203
|
+
* - `'close'` (default) — destructive shutdown. Connections release
|
|
204
|
+
* resources and become unusable. Use at real shutdown: process exit,
|
|
205
|
+
* extension deactivate, config-file change.
|
|
206
|
+
*
|
|
207
|
+
* - `'idle'` — reversible release. Connections release expensive
|
|
208
|
+
* resources (DuckDB file locks, socket pools) but stay logically
|
|
209
|
+
* valid. The same Connection objects are reused on next lookup;
|
|
210
|
+
* schema cache and other in-process state survive. Use between
|
|
211
|
+
* operations in long-lived hosts (a VSCode extension, an MCP server,
|
|
212
|
+
* any host that builds Runtimes per request) so that other writers
|
|
213
|
+
* can claim resources during idle gaps.
|
|
201
214
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* call this when a runtime goes out of scope; one-shot CLIs can skip it
|
|
205
|
-
* and let process exit clean up.
|
|
215
|
+
* A no-op for runtimes constructed without a MalloyConfig — in that
|
|
216
|
+
* case the caller owns the connections they passed in.
|
|
206
217
|
*/
|
|
207
|
-
async
|
|
218
|
+
async shutdown(connections = 'close') {
|
|
208
219
|
var _a;
|
|
209
|
-
await ((_a = this._config) === null || _a === void 0 ? void 0 : _a.
|
|
220
|
+
await ((_a = this._config) === null || _a === void 0 ? void 0 : _a.shutdown(connections));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* @deprecated Use `shutdown('close')` instead.
|
|
224
|
+
*/
|
|
225
|
+
async releaseConnections() {
|
|
226
|
+
await this.shutdown('close');
|
|
210
227
|
}
|
|
211
228
|
/**
|
|
212
229
|
* Load a Malloy model by URL or contents.
|
|
@@ -532,7 +549,7 @@ class ModelMaterializer extends FluentState {
|
|
|
532
549
|
const result = await this.loadQuery(searchMapMalloy, options).run({
|
|
533
550
|
rowLimit: 1000,
|
|
534
551
|
});
|
|
535
|
-
const rawResult = result.
|
|
552
|
+
const rawResult = result.data.toObject();
|
|
536
553
|
return rawResult.map(row => ({
|
|
537
554
|
...row,
|
|
538
555
|
cardinality: (0, row_data_utils_1.rowDataToNumber)(row.cardinality),
|
|
@@ -870,4 +887,18 @@ class ExploreMaterializer extends FluentState {
|
|
|
870
887
|
}
|
|
871
888
|
}
|
|
872
889
|
exports.ExploreMaterializer = ExploreMaterializer;
|
|
890
|
+
/**
|
|
891
|
+
* Structural check for the `BuildManifest` shape: a non-null object with an
|
|
892
|
+
* object `entries` field. Doesn't validate every entry — `BuildManifestEntry`
|
|
893
|
+
* is just `{tableName: string}`, so a stricter walk could come later if we
|
|
894
|
+
* find malformed entries causing trouble. The current goal is to fail
|
|
895
|
+
* cleanly on a manifest file that parsed to a string/array/null instead of
|
|
896
|
+
* leaving a downstream `entries[buildId]` lookup to crash on `undefined`.
|
|
897
|
+
*/
|
|
898
|
+
function isBuildManifestShape(value) {
|
|
899
|
+
if (typeof value !== 'object' || value === null)
|
|
900
|
+
return false;
|
|
901
|
+
const entries = value.entries;
|
|
902
|
+
return typeof entries === 'object' && entries !== null;
|
|
903
|
+
}
|
|
873
904
|
//# sourceMappingURL=runtime.js.map
|
|
@@ -40,6 +40,7 @@ export declare abstract class BaseConnection implements Connection {
|
|
|
40
40
|
canPersist(): this is PersistSQLResults;
|
|
41
41
|
canStream(): this is StreamingConnection;
|
|
42
42
|
close(): Promise<void>;
|
|
43
|
+
idle(): Promise<void>;
|
|
43
44
|
estimateQueryCost(_sqlCommand: string): Promise<QueryRunStats>;
|
|
44
45
|
fetchMetadata(): Promise<{}>;
|
|
45
46
|
fetchTableMetadata(_tablePath: string): Promise<{}>;
|
|
@@ -122,11 +122,14 @@ export declare function readConnectionsConfig(jsonText: string): ConnectionsConf
|
|
|
122
122
|
export declare function writeConnectionsConfig(config: ConnectionsConfig): string;
|
|
123
123
|
/**
|
|
124
124
|
* A LookupConnection with lifecycle management: close() shuts down all
|
|
125
|
-
* cached connections,
|
|
126
|
-
*
|
|
125
|
+
* cached connections, idle() releases their backend resources but keeps
|
|
126
|
+
* the cache so the same connection objects are reused on next lookup, and
|
|
127
|
+
* an optional onConnectionCreated callback fires once per connection after
|
|
128
|
+
* factory creation (before caching).
|
|
127
129
|
*/
|
|
128
130
|
export interface ManagedConnectionLookup extends LookupConnection<Connection> {
|
|
129
131
|
close(): Promise<void>;
|
|
132
|
+
idle(): Promise<void>;
|
|
130
133
|
}
|
|
131
134
|
/**
|
|
132
135
|
* Create a ManagedConnectionLookup from a ConnectionsConfig using registered
|
|
@@ -159,6 +159,13 @@ function createConnectionsFromConfig(config, onConnectionCreated) {
|
|
|
159
159
|
await conn.close();
|
|
160
160
|
}
|
|
161
161
|
},
|
|
162
|
+
async idle() {
|
|
163
|
+
// Cache is preserved — the same Connection objects are reused so that
|
|
164
|
+
// schema cache and other in-process state survive the idle.
|
|
165
|
+
for (const conn of cache.values()) {
|
|
166
|
+
await conn.idle();
|
|
167
|
+
}
|
|
168
|
+
},
|
|
162
169
|
};
|
|
163
170
|
}
|
|
164
171
|
//# sourceMappingURL=registry.js.map
|
|
@@ -77,6 +77,20 @@ export interface Connection extends InfoConnection {
|
|
|
77
77
|
canPersist(): this is PersistSQLResults;
|
|
78
78
|
canStream(): this is StreamingConnection;
|
|
79
79
|
close(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Release expensive backend resources (file locks, sockets, sub-processes,
|
|
82
|
+
* pooled connections) but remain logically valid. The next operation
|
|
83
|
+
* transparently reattaches whatever was released; schema cache and other
|
|
84
|
+
* in-process state survive.
|
|
85
|
+
*
|
|
86
|
+
* The default is a no-op for backends that hold no release-able resources
|
|
87
|
+
* between operations. Backends that hold OS-level resources (DuckDB file
|
|
88
|
+
* locks, persistent socket pools) should override.
|
|
89
|
+
*
|
|
90
|
+
* Hosts that share a connection across concurrent operations should not
|
|
91
|
+
* call `idle()` while an operation is in flight.
|
|
92
|
+
*/
|
|
93
|
+
idle(): Promise<void>;
|
|
80
94
|
estimateQueryCost(sqlCommand: string): Promise<QueryRunStats>;
|
|
81
95
|
fetchMetadata: () => Promise<ConnectionMetadata>;
|
|
82
96
|
fetchTableMetadata: (tablePath: string) => Promise<TableMetadata>;
|
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
* RULE: BLOCK BODY
|
|
7
7
|
*
|
|
8
8
|
* A `{ … }` body containing statements (extend body, view body, etc.). Walks
|
|
9
|
-
* children; between adjacent statements
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* children; between adjacent statements:
|
|
10
|
+
* - `view:` definitions always get a blank line before them (and after
|
|
11
|
+
* the previous one), regardless of whether the source had a blank or
|
|
12
|
+
* what kind preceded — view definitions read as their own sections.
|
|
13
|
+
* - For other kinds: preserve a single user-supplied blank line only if
|
|
14
|
+
* the kinds differ. Same-kind adjacent statements (consecutive
|
|
15
|
+
* dimensions, measures, etc.) never get a blank.
|
|
12
16
|
*
|
|
13
17
|
* Also: top-level body — forces a blank line before each statement after the
|
|
14
18
|
* first, regardless of source spacing (top-level statements should breathe).
|
|
@@ -61,9 +65,18 @@ function formatBlockBody(f, ctx) {
|
|
|
61
65
|
if (c instanceof antlr4ts_1.ParserRuleContext) {
|
|
62
66
|
if (lastChild !== null) {
|
|
63
67
|
const userHadBlank = c._start.line - lastChildEndLine > 1;
|
|
64
|
-
const
|
|
65
|
-
|
|
68
|
+
const lastKind = statementKind(lastChild);
|
|
69
|
+
const curKind = statementKind(c);
|
|
70
|
+
const sameKind = lastKind === curKind;
|
|
71
|
+
// `view:` definitions always breathe — blank line above each one
|
|
72
|
+
// (and after the previous one) regardless of the user's source
|
|
73
|
+
// spacing or what kind preceded.
|
|
74
|
+
if (curKind === 'view' || lastKind === 'view') {
|
|
66
75
|
f.o.blank();
|
|
76
|
+
}
|
|
77
|
+
else if (userHadBlank && !sameKind) {
|
|
78
|
+
f.o.blank();
|
|
79
|
+
}
|
|
67
80
|
}
|
|
68
81
|
f.format(c);
|
|
69
82
|
lastChild = c;
|
|
@@ -60,6 +60,7 @@ const sections_1 = require("./sections");
|
|
|
60
60
|
const field_properties_1 = require("./field-properties");
|
|
61
61
|
const pick_case_1 = require("./pick-case");
|
|
62
62
|
const binary_chain_1 = require("./binary-chain");
|
|
63
|
+
const import_select_1 = require("./import-select");
|
|
63
64
|
class Formatter {
|
|
64
65
|
constructor(src, tokens) {
|
|
65
66
|
this.src = src;
|
|
@@ -123,6 +124,10 @@ class Formatter {
|
|
|
123
124
|
return (0, sections_1.formatSectionStatement)(this, node, rule);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
// RULE: IMPORT SELECT — `import {a, b} from 'x'` stays compact.
|
|
128
|
+
if (node instanceof parser.ImportSelectContext) {
|
|
129
|
+
return (0, import_select_1.formatImportSelect)(this, node);
|
|
130
|
+
}
|
|
126
131
|
// RULE: PICK / CASE / BINARY CHAIN.
|
|
127
132
|
if (node instanceof parser.PickStatementContext)
|
|
128
133
|
return (0, pick_case_1.formatPickStatement)(this, node);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* RULE: IMPORT SELECT — `import {a, b, c} from 'url'`
|
|
7
|
+
*
|
|
8
|
+
* The selection list is `{ id (IS id)? (, id (IS id)?)* }`. Inline if the
|
|
9
|
+
* whole brace-and-contents fits on the current line. Otherwise wrap with
|
|
10
|
+
* each item on its own line at +1 indent.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.formatImportSelect = formatImportSelect;
|
|
14
|
+
const tokens_1 = require("./tokens");
|
|
15
|
+
const leaf_1 = require("./leaf");
|
|
16
|
+
const inline_renderer_1 = require("./inline-renderer");
|
|
17
|
+
function formatImportSelect(f, ctx) {
|
|
18
|
+
// Flush hidden tokens between the previous emit (IMPORT) and our opener
|
|
19
|
+
// so a comment like `import /* tag */ {a} from 'x'` is preserved.
|
|
20
|
+
(0, leaf_1.flushHiddenBefore)(f, ctx._start.tokenIndex);
|
|
21
|
+
const items = ctx.importItem();
|
|
22
|
+
// Grammar puts FROM as the last token of importSelect: emit it ourselves
|
|
23
|
+
// so the trailing space and lastEmittedIdx land correctly.
|
|
24
|
+
const fromTok = ctx.FROM().symbol;
|
|
25
|
+
const fromIdx = fromTok.tokenIndex;
|
|
26
|
+
if (items.length === 0) {
|
|
27
|
+
f.o.space();
|
|
28
|
+
f.o.text('{} from');
|
|
29
|
+
(0, leaf_1.note)(f, tokens_1.L.FROM, fromIdx, fromTok);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const firstItem = items[0];
|
|
33
|
+
const lastItem = items[items.length - 1];
|
|
34
|
+
// Comments anywhere in the items' span (between items, inside an `as is`
|
|
35
|
+
// form, etc.) get stripped by renderItemInline. Fall back to a comment-
|
|
36
|
+
// safe wrap that emits each item via f.format — the leaf walker handles
|
|
37
|
+
// hidden-channel placement.
|
|
38
|
+
const itemsHaveComments = (0, leaf_1.hasCommentsInRange)(f, firstItem._start.tokenIndex, lastItem._stop.tokenIndex);
|
|
39
|
+
if (!itemsHaveComments) {
|
|
40
|
+
const itemStrs = items.map(it => (0, inline_renderer_1.renderItemInline)(f, it));
|
|
41
|
+
const inlineBody = '{' + itemStrs.join(', ') + '} from';
|
|
42
|
+
if (f.o.lineLengthSoFar() + 1 + inlineBody.length <= tokens_1.LINE_BUDGET) {
|
|
43
|
+
f.o.space();
|
|
44
|
+
f.o.text(inlineBody);
|
|
45
|
+
(0, leaf_1.note)(f, tokens_1.L.FROM, fromIdx, fromTok);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Wrap form (no comments): one item per line at +1 indent. Pre-rendered
|
|
49
|
+
// text is fine because renderItemInline saw no comments to drop.
|
|
50
|
+
f.o.space();
|
|
51
|
+
f.o.text('{');
|
|
52
|
+
f.o.indent++;
|
|
53
|
+
for (let i = 0; i < itemStrs.length; i++) {
|
|
54
|
+
f.o.nl();
|
|
55
|
+
f.o.text(itemStrs[i]);
|
|
56
|
+
if (i < itemStrs.length - 1)
|
|
57
|
+
f.o.text(',');
|
|
58
|
+
}
|
|
59
|
+
f.o.indent--;
|
|
60
|
+
f.o.nl();
|
|
61
|
+
f.o.text('} from');
|
|
62
|
+
(0, leaf_1.note)(f, tokens_1.L.FROM, fromIdx, fromTok);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Comment-safe wrap: each item emits via f.format so flushHiddenBefore
|
|
66
|
+
// can place its leading/inter-item comments correctly. Trailing `,` after
|
|
67
|
+
// each non-last item, leaf walker turns it into a newline at indent.
|
|
68
|
+
f.o.space();
|
|
69
|
+
f.o.text('{');
|
|
70
|
+
f.o.indent++;
|
|
71
|
+
// Advance past the OCURLY we just emitted manually so the first item's
|
|
72
|
+
// flushHiddenBefore doesn't try to re-emit it.
|
|
73
|
+
f.lastEmittedIdx = ctx._start.tokenIndex;
|
|
74
|
+
for (let i = 0; i < items.length; i++) {
|
|
75
|
+
(0, leaf_1.flushHiddenBefore)(f, items[i]._start.tokenIndex);
|
|
76
|
+
f.o.nl();
|
|
77
|
+
f.format(items[i]);
|
|
78
|
+
if (i < items.length - 1)
|
|
79
|
+
f.o.text(',');
|
|
80
|
+
}
|
|
81
|
+
// Catch any tail comments between the last item and the closing `}`.
|
|
82
|
+
(0, leaf_1.flushHiddenBefore)(f, fromIdx);
|
|
83
|
+
f.o.indent--;
|
|
84
|
+
f.o.nl();
|
|
85
|
+
f.o.text('} from');
|
|
86
|
+
(0, leaf_1.note)(f, tokens_1.L.FROM, fromIdx, fromTok);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=import-select.js.map
|
|
@@ -57,6 +57,30 @@
|
|
|
57
57
|
* - Single-arg function calls don't wrap (no point — nowhere useful to break).
|
|
58
58
|
* - `(` hugs only after a known-callable token (CALL_HUG_AFTER); after `is`,
|
|
59
59
|
* `as`, `extend`, `on`, `when`, etc. the `(` is grouping and gets a space.
|
|
60
|
+
* CALL_HUG_AFTER includes the keyword-named built-ins that are commonly
|
|
61
|
+
* used as functions: ALL, EXCLUDE, the timeframe truncation keywords
|
|
62
|
+
* (YEAR/MONTH/DAY/…), and the cast-target type names
|
|
63
|
+
* (TIMESTAMP/DATE/NUMBER/STRING/BOOLEAN/JSON).
|
|
64
|
+
* - `!` is the cast operator (`epoch_ms!timestamp(x)`); it glues to both
|
|
65
|
+
* sides like `.` does.
|
|
66
|
+
* - Empty `{}` collapses inline (`extend {}`, not `extend {\n}`).
|
|
67
|
+
* - `import {a, b, c} from 'x'` formats as a flat list when it fits on the
|
|
68
|
+
* line; otherwise one item per line at +1 indent.
|
|
69
|
+
* - `view:` definitions in a block body always have a blank line before
|
|
70
|
+
* each one (after the first), even when no blank was in the source. The
|
|
71
|
+
* same-kind-no-blank rule still applies to other statement kinds.
|
|
72
|
+
* - `{...}` bodies that contain more than one section statement never
|
|
73
|
+
* collapse onto a single line, even when they would fit. Reading two
|
|
74
|
+
* `group_by:` clauses jammed on one line is hostile.
|
|
75
|
+
* - Single is-item section lists keep the keyword and item on the same
|
|
76
|
+
* line (`nest: name is { … }`), so the body wraps naturally instead of
|
|
77
|
+
* forcing a `nest:\n name is {` opener split.
|
|
78
|
+
* - join_one / join_many / join_cross multi-item lists always wrap one
|
|
79
|
+
* item per line — items use `with`/`on` instead of `is` but are
|
|
80
|
+
* structurally is-like.
|
|
81
|
+
* - Trailing comments between the last item of a section list and the
|
|
82
|
+
* enclosing `}` are emitted at the section's inner indent, so they
|
|
83
|
+
* stay associated with the section the user wrote them in.
|
|
60
84
|
*
|
|
61
85
|
* Adding a new section-statement
|
|
62
86
|
* ------------------------------
|
|
@@ -127,6 +127,18 @@ function hasCommentsInRange(f, fromIdx, toIdx) {
|
|
|
127
127
|
}
|
|
128
128
|
return false;
|
|
129
129
|
}
|
|
130
|
+
// Index of the next non-hidden, non-EOF token strictly after `idx`, or -1.
|
|
131
|
+
function nextVisibleAfter(f, idx) {
|
|
132
|
+
for (let j = idx + 1; j < f.tokens.length; j++) {
|
|
133
|
+
const t = f.tokens[j];
|
|
134
|
+
if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
|
|
135
|
+
continue;
|
|
136
|
+
if (t.type === antlr4ts_1.Token.EOF)
|
|
137
|
+
return -1;
|
|
138
|
+
return j;
|
|
139
|
+
}
|
|
140
|
+
return -1;
|
|
141
|
+
}
|
|
130
142
|
// Does the paren-pair at [openIdx, closeIdx] have any COMMA at its own depth?
|
|
131
143
|
// (Used to distinguish "function call with multiple args" from "single-arg
|
|
132
144
|
// call" / "empty parens".)
|
|
@@ -190,6 +202,21 @@ function emitVisibleToken(f, t, idx) {
|
|
|
190
202
|
if (t.type === tokens_1.L.OCURLY) {
|
|
191
203
|
f.o.space();
|
|
192
204
|
f.o.text('{');
|
|
205
|
+
// Empty `{}`: peek the next visible token. If it's the matching close
|
|
206
|
+
// AND nothing hidden sits between them (no comments to preserve), emit
|
|
207
|
+
// inline so we get `extend {}` not `extend {\n}`. With a comment in the
|
|
208
|
+
// gap (`extend { /* keep */ }`), fall through to the wrapping form so
|
|
209
|
+
// the leaf walker's comment placement runs.
|
|
210
|
+
const nextVisible = nextVisibleAfter(f, idx);
|
|
211
|
+
if (nextVisible !== -1 &&
|
|
212
|
+
f.tokens[nextVisible].type === tokens_1.L.CCURLY &&
|
|
213
|
+
!hasCommentsInRange(f, idx + 1, nextVisible - 1)) {
|
|
214
|
+
f.o.text('}');
|
|
215
|
+
if (f.o.indent === 0)
|
|
216
|
+
f.needBlank = true;
|
|
217
|
+
note(f, tokens_1.L.CCURLY, nextVisible, f.tokens[nextVisible]);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
193
220
|
f.o.indent++;
|
|
194
221
|
f.o.nl();
|
|
195
222
|
note(f, t.type, idx, t);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ParserRuleContext } from 'antlr4ts';
|
|
2
|
-
export type ItemKind = 'fieldEntry' | 'nestEntry' | 'fieldDef' | 'fieldName' | 'collectionMember' | 'orderBySpec' | 'fieldExpr';
|
|
2
|
+
export type ItemKind = 'fieldEntry' | 'nestEntry' | 'fieldDef' | 'fieldName' | 'collectionMember' | 'orderBySpec' | 'fieldExpr' | 'joinDef' | 'includeField' | 'indexElement';
|
|
3
3
|
export interface SectionRule {
|
|
4
4
|
ctxClass: new (...args: never[]) => ParserRuleContext;
|
|
5
5
|
keywordTypes: number[];
|
|
@@ -79,6 +79,14 @@ exports.SECTION_STATEMENT_RULES = [
|
|
|
79
79
|
rule(parser.OrderByStatementContext, [tokens_1.L.ORDER_BY], c => c.ordering(), 'orderBySpec'),
|
|
80
80
|
rule(parser.WhereStatementContext, [tokens_1.L.WHERE], c => c.filterClauseList(), 'fieldExpr'),
|
|
81
81
|
rule(parser.HavingStatementContext, [tokens_1.L.HAVING], c => c.filterClauseList(), 'fieldExpr'),
|
|
82
|
+
rule(parser.DefJoinOneContext, [tokens_1.L.JOIN_ONE], c => c.joinList(), 'joinDef'),
|
|
83
|
+
rule(parser.DefJoinManyContext, [tokens_1.L.JOIN_MANY], c => c.joinList(), 'joinDef'),
|
|
84
|
+
rule(parser.DefJoinCrossContext, [tokens_1.L.JOIN_CROSS], c => c.joinList(), 'joinDef'),
|
|
85
|
+
// include block items: `public: a, b`, `internal: x, y, z`. The keyword
|
|
86
|
+
// (PUBLIC/PRIVATE/INTERNAL) lives one level down inside accessLabelProp;
|
|
87
|
+
// findKeyword in ./sections handles that nested case.
|
|
88
|
+
rule(parser.IncludeItemContext, [tokens_1.L.PUBLIC, tokens_1.L.PRIVATE, tokens_1.L.INTERNAL], c => c.includeList(), 'includeField'),
|
|
89
|
+
rule(parser.IndexStatementContext, [tokens_1.L.INDEX], c => c.indexFields(), 'indexElement'),
|
|
82
90
|
];
|
|
83
91
|
// Coarse statement-kind labels for the same-kind-no-blank rule in block
|
|
84
92
|
// bodies. Different kinds preserve a single user-supplied blank line.
|
|
@@ -13,11 +13,21 @@
|
|
|
13
13
|
*
|
|
14
14
|
* formatSectionList rule (locked in with the user):
|
|
15
15
|
* - All bare items + total fits ≤ LINE_BUDGET → inline `kw: a, b, c`.
|
|
16
|
-
* - Single item that fits (even if it has `is`) → inline.
|
|
16
|
+
* - Single item that fits (even if it has `is`) → inline. Items containing
|
|
17
|
+
* a `{...}` body with more than one section statement are excluded —
|
|
18
|
+
* view bodies don't read on one line regardless of length.
|
|
19
|
+
* - Single is-item that doesn't fit inline → keep keyword and item on the
|
|
20
|
+
* same line (`nest: name is { …wrapped body… }`); the body's `{...}`
|
|
21
|
+
* wraps internally. Annotated items still take the keyword-on-own-line
|
|
22
|
+
* form so the annotation lands above its item.
|
|
17
23
|
* - Otherwise → wrapped: keyword on its own line; items at +1 indent;
|
|
18
24
|
* bare items flow-fill ≤ LINE_BUDGET, comma-separated intra-line,
|
|
19
25
|
* no trailing commas; `is` items each on own line; annotated items
|
|
20
26
|
* each on own line, annotation on the line above.
|
|
27
|
+
*
|
|
28
|
+
* After the last item, trailing comments that sit between the section and
|
|
29
|
+
* the enclosing `}` are also emitted at the inner indent — they belong to
|
|
30
|
+
* the section the user just wrote, not the parent block.
|
|
21
31
|
*/
|
|
22
32
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
33
|
if (k2 === undefined) k2 = k;
|
|
@@ -70,7 +80,9 @@ function formatSectionStatement(f, stmt, rule) {
|
|
|
70
80
|
}
|
|
71
81
|
for (let i = 0; i < stmt.childCount; i++) {
|
|
72
82
|
const c = stmt.getChild(i);
|
|
73
|
-
|
|
83
|
+
const isKeywordChild = (c instanceof tree_1.TerminalNode && c.symbol === keywordTok) ||
|
|
84
|
+
(c instanceof antlr4ts_1.ParserRuleContext && childContainsToken(c, keywordTok));
|
|
85
|
+
if (isKeywordChild) {
|
|
74
86
|
(0, leaf_1.flushHiddenBefore)(f, keywordTok.tokenIndex);
|
|
75
87
|
if (f.o.indent > 0)
|
|
76
88
|
f.o.nl();
|
|
@@ -83,18 +95,39 @@ function formatSectionStatement(f, stmt, rule) {
|
|
|
83
95
|
if (c === listCtx) {
|
|
84
96
|
const items = listItems(listCtx, rule.itemKind);
|
|
85
97
|
if (items.length > 0)
|
|
86
|
-
formatSectionList(f, items);
|
|
98
|
+
formatSectionList(f, items, rule.itemKind);
|
|
87
99
|
continue;
|
|
88
100
|
}
|
|
89
101
|
f.format(c);
|
|
90
102
|
}
|
|
91
103
|
}
|
|
104
|
+
function childContainsToken(node, tok) {
|
|
105
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
106
|
+
const c = node.getChild(i);
|
|
107
|
+
if (c instanceof tree_1.TerminalNode && c.symbol === tok)
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
92
112
|
function findKeyword(node, types) {
|
|
113
|
+
// Direct terminal children — fast path, the common case.
|
|
93
114
|
for (let i = 0; i < node.childCount; i++) {
|
|
94
115
|
const c = node.getChild(i);
|
|
95
116
|
if (c instanceof tree_1.TerminalNode && types.includes(c.symbol.type))
|
|
96
117
|
return c.symbol;
|
|
97
118
|
}
|
|
119
|
+
// Fallback: descend one level. Some rules wrap the keyword in a small
|
|
120
|
+
// nested rule (e.g. includeItem → accessLabelProp → INTERNAL).
|
|
121
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
122
|
+
const c = node.getChild(i);
|
|
123
|
+
if (c instanceof antlr4ts_1.ParserRuleContext) {
|
|
124
|
+
for (let j = 0; j < c.childCount; j++) {
|
|
125
|
+
const g = c.getChild(j);
|
|
126
|
+
if (g instanceof tree_1.TerminalNode && types.includes(g.symbol.type))
|
|
127
|
+
return g.symbol;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
98
131
|
return undefined;
|
|
99
132
|
}
|
|
100
133
|
function listItems(listCtx, itemKind) {
|
|
@@ -117,6 +150,12 @@ function listItems(listCtx, itemKind) {
|
|
|
117
150
|
return c instanceof parser.OrderBySpecContext;
|
|
118
151
|
case 'fieldExpr':
|
|
119
152
|
return c instanceof parser.FieldExprContext;
|
|
153
|
+
case 'joinDef':
|
|
154
|
+
return c instanceof parser.JoinDefContext;
|
|
155
|
+
case 'includeField':
|
|
156
|
+
return c instanceof parser.IncludeFieldContext;
|
|
157
|
+
case 'indexElement':
|
|
158
|
+
return c instanceof parser.IndexElementContext;
|
|
120
159
|
}
|
|
121
160
|
};
|
|
122
161
|
const out = [];
|
|
@@ -127,8 +166,10 @@ function listItems(listCtx, itemKind) {
|
|
|
127
166
|
}
|
|
128
167
|
return out;
|
|
129
168
|
}
|
|
130
|
-
function classifyItem(f, ctx) {
|
|
131
|
-
|
|
169
|
+
function classifyItem(f, ctx, itemKind) {
|
|
170
|
+
// joinDef items always wrap onto their own line. They use `with` / `on`
|
|
171
|
+
// instead of `is`, but they're structurally one-per-line like is-items.
|
|
172
|
+
let hasIs = itemKind === 'joinDef';
|
|
132
173
|
let hasAnnotation = false;
|
|
133
174
|
for (let i = ctx._start.tokenIndex; i <= ctx._stop.tokenIndex; i++) {
|
|
134
175
|
const t = f.tokens[i];
|
|
@@ -142,17 +183,23 @@ function classifyItem(f, ctx) {
|
|
|
142
183
|
}
|
|
143
184
|
return { ctx, hasIs, hasAnnotation };
|
|
144
185
|
}
|
|
145
|
-
function formatSectionList(f, items) {
|
|
146
|
-
const itemInfos = items.map(it => classifyItem(f, it));
|
|
186
|
+
function formatSectionList(f, items, itemKind) {
|
|
187
|
+
const itemInfos = items.map(it => classifyItem(f, it, itemKind));
|
|
147
188
|
const noAnnotations = itemInfos.every(info => !info.hasAnnotation);
|
|
148
189
|
const allBare = itemInfos.every(info => !info.hasIs && !info.hasAnnotation);
|
|
149
190
|
const firstItem = items[0];
|
|
150
191
|
const lastItem = items[items.length - 1];
|
|
151
192
|
// Inline candidate: no annotations, no hidden-channel comments anywhere in
|
|
152
193
|
// the items' span (renderItemInline drops them), AND either all bare or
|
|
153
|
-
// exactly one item.
|
|
194
|
+
// exactly one item. Items containing a `{...}` body with multiple inner
|
|
195
|
+
// statements are also excluded — collapsing a view body onto one line is
|
|
196
|
+
// hostile to read regardless of length.
|
|
154
197
|
const itemsHaveComments = (0, leaf_1.hasCommentsInRange)(f, firstItem._start.tokenIndex, lastItem._stop.tokenIndex);
|
|
155
|
-
const
|
|
198
|
+
const itemsHaveMultiStatementBody = itemInfos.some(info => hasMultiStatementCurlyBody(f, info.ctx));
|
|
199
|
+
const inlineEligible = noAnnotations &&
|
|
200
|
+
!itemsHaveComments &&
|
|
201
|
+
!itemsHaveMultiStatementBody &&
|
|
202
|
+
(allBare || items.length === 1);
|
|
156
203
|
if (inlineEligible) {
|
|
157
204
|
const renderedItems = itemInfos.map(info => (0, inline_renderer_1.renderItemInline)(f, info.ctx));
|
|
158
205
|
const inlineBody = renderedItems.join(', ');
|
|
@@ -164,6 +211,20 @@ function formatSectionList(f, items) {
|
|
|
164
211
|
return;
|
|
165
212
|
}
|
|
166
213
|
}
|
|
214
|
+
// Single is-item that doesn't fit inline: keep the keyword on the same
|
|
215
|
+
// line as the item (`nest: name is { …wrapped body… }`) instead of
|
|
216
|
+
// breaking before the name. The body's `{...}` will wrap on its own.
|
|
217
|
+
// Annotated items still need the keyword-on-own-line form so the
|
|
218
|
+
// annotation can land between them.
|
|
219
|
+
if (items.length === 1 &&
|
|
220
|
+
itemInfos[0].hasIs &&
|
|
221
|
+
!itemInfos[0].hasAnnotation &&
|
|
222
|
+
!itemsHaveComments) {
|
|
223
|
+
f.o.text(' ');
|
|
224
|
+
f.format(itemInfos[0].ctx);
|
|
225
|
+
f.lastEmittedType = lastItem._stop.type;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
167
228
|
// Wrapped form. Two paths:
|
|
168
229
|
// - No comments anywhere: original flow-fill — bare items pack into lines
|
|
169
230
|
// at LINE_BUDGET, `is`/annotated items each get their own line.
|
|
@@ -230,14 +291,56 @@ function formatSectionList(f, items) {
|
|
|
230
291
|
// lastEmittedIdx so tail comments aren't re-emitted by the parent.
|
|
231
292
|
f.lastEmittedType = lastItem._stop.type;
|
|
232
293
|
}
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
294
|
+
// Does any `{...}` block inside this item contain more than one section
|
|
295
|
+
// keyword (group_by:, aggregate:, where:, …) at its top level? Used to gate
|
|
296
|
+
// the section-list inline form: a view body with multiple statements never
|
|
297
|
+
// reads well on a single line, even when it fits. Single-statement bodies
|
|
298
|
+
// like `{ where: x = 1 }` may still inline.
|
|
299
|
+
function hasMultiStatementCurlyBody(f, ctx) {
|
|
300
|
+
const fromIdx = ctx._start.tokenIndex;
|
|
301
|
+
const toIdx = ctx._stop.tokenIndex;
|
|
302
|
+
for (let i = fromIdx; i <= toIdx; i++) {
|
|
303
|
+
if (f.tokens[i].type !== tokens_1.L.OCURLY)
|
|
304
|
+
continue;
|
|
305
|
+
const close = (0, tokens_1.findMatching)(f.tokens, i, tokens_1.L.OCURLY, tokens_1.L.CCURLY);
|
|
306
|
+
let count = 0;
|
|
307
|
+
let depth = 0;
|
|
308
|
+
for (let j = i + 1; j < close; j++) {
|
|
309
|
+
const t = f.tokens[j];
|
|
310
|
+
if (t.type === tokens_1.L.OCURLY || t.type === tokens_1.L.OPAREN || t.type === tokens_1.L.OBRACK) {
|
|
311
|
+
depth++;
|
|
312
|
+
}
|
|
313
|
+
else if (t.type === tokens_1.L.CCURLY ||
|
|
314
|
+
t.type === tokens_1.L.CPAREN ||
|
|
315
|
+
t.type === tokens_1.L.CBRACK) {
|
|
316
|
+
depth--;
|
|
317
|
+
}
|
|
318
|
+
else if (depth === 0 && tokens_1.SECTION_TOKENS.has(t.type)) {
|
|
319
|
+
count++;
|
|
320
|
+
if (count > 1)
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
i = close;
|
|
325
|
+
}
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
// After the last item of a wrapped section list, flush trailing comments
|
|
329
|
+
// belonging to the section. There are two cases:
|
|
330
|
+
//
|
|
331
|
+
// 1. Same-line tail: a comment on the SAME source line as the last item.
|
|
332
|
+
// Always belongs to the last item; emit at the section's inner indent.
|
|
333
|
+
//
|
|
334
|
+
// 2. Different-line trailing comments that sit between the last item and
|
|
335
|
+
// the closing `}` of the enclosing block (no other statement follows).
|
|
336
|
+
// These visually belong to the section the user just wrote, not the
|
|
337
|
+
// block. Emit them at the inner indent so they stay associated with
|
|
338
|
+
// the section. If a real statement follows the comments, leave them
|
|
339
|
+
// for the parent — they're leading comments for that statement.
|
|
238
340
|
function flushSameLineTail(f, lastTok) {
|
|
239
341
|
const lastEndLine = (0, tokens_1.endLineOf)(lastTok);
|
|
240
342
|
let j = lastTok.tokenIndex + 1;
|
|
343
|
+
// Phase 1: same-line tail comments.
|
|
241
344
|
while (j < f.tokens.length) {
|
|
242
345
|
const t = f.tokens[j];
|
|
243
346
|
if (t.channel !== antlr4ts_1.Token.HIDDEN_CHANNEL)
|
|
@@ -247,15 +350,31 @@ function flushSameLineTail(f, lastTok) {
|
|
|
247
350
|
j++;
|
|
248
351
|
}
|
|
249
352
|
if (j > lastTok.tokenIndex + 1) {
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
// newline back for EOL comments). Without this, the comment lands on a
|
|
255
|
-
// new line, and a re-parse sees it as a different-line comment, breaking
|
|
256
|
-
// idempotence.
|
|
353
|
+
// Same-line comments: drop the wrapping loop's per-item newline so
|
|
354
|
+
// emitHiddenToken's same-line branch reattaches the comment correctly
|
|
355
|
+
// (and adds a trailing newline back for EOL comments). Otherwise a
|
|
356
|
+
// re-parse sees a different-line comment, breaking idempotence.
|
|
257
357
|
f.o.trimTrailingNewlines();
|
|
258
358
|
(0, leaf_1.flushHiddenBefore)(f, j);
|
|
259
359
|
}
|
|
360
|
+
// Phase 2: own-line comments before the next visible token. If the next
|
|
361
|
+
// visible token is a closing `}`, the comments visually belong to this
|
|
362
|
+
// section — emit them at the inner indent. Otherwise leave them for the
|
|
363
|
+
// parent (they're leading comments for whatever follows).
|
|
364
|
+
let k = j;
|
|
365
|
+
let trailingHidden = 0;
|
|
366
|
+
while (k < f.tokens.length) {
|
|
367
|
+
const t = f.tokens[k];
|
|
368
|
+
if (t.channel !== antlr4ts_1.Token.HIDDEN_CHANNEL)
|
|
369
|
+
break;
|
|
370
|
+
trailingHidden++;
|
|
371
|
+
k++;
|
|
372
|
+
}
|
|
373
|
+
if (trailingHidden > 0 && k < f.tokens.length) {
|
|
374
|
+
const next = f.tokens[k];
|
|
375
|
+
if (next.type === tokens_1.L.CCURLY) {
|
|
376
|
+
(0, leaf_1.flushHiddenBefore)(f, k);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
260
379
|
}
|
|
261
380
|
//# sourceMappingURL=sections.js.map
|
|
@@ -87,6 +87,26 @@ exports.CALL_HUG_AFTER = new Set([
|
|
|
87
87
|
exports.L.CAST,
|
|
88
88
|
exports.L.NOW,
|
|
89
89
|
exports.L.LAST,
|
|
90
|
+
// Ungrouped / level-modifier function-style calls.
|
|
91
|
+
exports.L.ALL,
|
|
92
|
+
exports.L.EXCLUDE,
|
|
93
|
+
// Timeframe truncation keywords used as functions: year(x), month(x), …
|
|
94
|
+
exports.L.YEAR,
|
|
95
|
+
exports.L.QUARTER,
|
|
96
|
+
exports.L.MONTH,
|
|
97
|
+
exports.L.WEEK,
|
|
98
|
+
exports.L.DAY,
|
|
99
|
+
exports.L.HOUR,
|
|
100
|
+
exports.L.MINUTE,
|
|
101
|
+
exports.L.SECOND,
|
|
102
|
+
// Type-cast keyword names: timestamp(x), date(x), number(x), string(x), …
|
|
103
|
+
exports.L.TIMESTAMP,
|
|
104
|
+
exports.L.TIMESTAMPTZ,
|
|
105
|
+
exports.L.DATE,
|
|
106
|
+
exports.L.NUMBER,
|
|
107
|
+
exports.L.STRING,
|
|
108
|
+
exports.L.BOOLEAN,
|
|
109
|
+
exports.L.JSON,
|
|
90
110
|
]);
|
|
91
111
|
// Binary operators that get spaces on both sides at the leaf level.
|
|
92
112
|
// (Chain wrapping for and/or/??/+/- happens at parse-tree level — see
|
|
@@ -119,10 +139,15 @@ function leadingAction(prevType, nextType) {
|
|
|
119
139
|
nextType === exports.L.SEMI ||
|
|
120
140
|
nextType === exports.L.COLON ||
|
|
121
141
|
nextType === exports.L.TRIPLECOLON ||
|
|
142
|
+
nextType === exports.L.EXCLAM ||
|
|
122
143
|
nextType === exports.L.CPAREN ||
|
|
123
144
|
nextType === exports.L.CBRACK) {
|
|
124
145
|
return 'glue';
|
|
125
146
|
}
|
|
147
|
+
// After the `!` cast operator (`epoch_ms!timestamp(x)`), the next token
|
|
148
|
+
// glues to it like the `.` operator does.
|
|
149
|
+
if (prevType === exports.L.EXCLAM)
|
|
150
|
+
return 'glue';
|
|
126
151
|
if ((nextType === exports.L.OPAREN || nextType === exports.L.OBRACK) &&
|
|
127
152
|
prevType !== null &&
|
|
128
153
|
exports.CALL_HUG_AFTER.has(prevType)) {
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MALLOY_VERSION = "0.0.
|
|
1
|
+
export declare const MALLOY_VERSION = "0.0.388";
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MALLOY_VERSION = void 0;
|
|
4
4
|
// generated with 'generate-version-file' script; do not edit manually
|
|
5
|
-
exports.MALLOY_VERSION = '0.0.
|
|
5
|
+
exports.MALLOY_VERSION = '0.0.388';
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/malloy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.388",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@malloydata/malloy-filter": "0.0.
|
|
55
|
-
"@malloydata/malloy-interfaces": "0.0.
|
|
56
|
-
"@malloydata/malloy-tag": "0.0.
|
|
54
|
+
"@malloydata/malloy-filter": "0.0.388",
|
|
55
|
+
"@malloydata/malloy-interfaces": "0.0.388",
|
|
56
|
+
"@malloydata/malloy-tag": "0.0.388",
|
|
57
57
|
"@noble/hashes": "^1.8.0",
|
|
58
58
|
"antlr4ts": "^0.5.0-alpha.4",
|
|
59
59
|
"assert": "^2.0.0",
|