@nowline/lsp 0.8.5 → 0.9.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.
Files changed (34) hide show
  1. package/dist/providers/completion.d.ts +7 -1
  2. package/dist/providers/completion.d.ts.map +1 -1
  3. package/dist/providers/completion.js +42 -32
  4. package/dist/providers/completion.js.map +1 -1
  5. package/dist/providers/definition.d.ts +1 -1
  6. package/dist/providers/definition.d.ts.map +1 -1
  7. package/dist/providers/definition.js +1 -1
  8. package/dist/providers/document-symbols.d.ts +2 -1
  9. package/dist/providers/document-symbols.d.ts.map +1 -1
  10. package/dist/providers/document-symbols.js +25 -22
  11. package/dist/providers/document-symbols.js.map +1 -1
  12. package/dist/providers/hover.d.ts +9 -0
  13. package/dist/providers/hover.d.ts.map +1 -1
  14. package/dist/providers/hover.js +109 -14
  15. package/dist/providers/hover.js.map +1 -1
  16. package/dist/providers/references.d.ts.map +1 -1
  17. package/dist/providers/references.js +3 -1
  18. package/dist/providers/references.js.map +1 -1
  19. package/dist/providers/rename.d.ts.map +1 -1
  20. package/dist/providers/rename.js +2 -2
  21. package/dist/providers/rename.js.map +1 -1
  22. package/dist/references/ast-utils.d.ts +41 -31
  23. package/dist/references/ast-utils.d.ts.map +1 -1
  24. package/dist/references/ast-utils.js +54 -129
  25. package/dist/references/ast-utils.js.map +1 -1
  26. package/dist/server.js.map +1 -1
  27. package/package.json +10 -10
  28. package/src/providers/completion.ts +42 -31
  29. package/src/providers/definition.ts +1 -1
  30. package/src/providers/document-symbols.ts +31 -21
  31. package/src/providers/hover.ts +124 -14
  32. package/src/providers/references.ts +2 -0
  33. package/src/providers/rename.ts +2 -1
  34. package/src/references/ast-utils.ts +81 -127
@@ -3,21 +3,21 @@ import type {
3
3
  EntityProperty,
4
4
  FootnoteDeclaration,
5
5
  GroupBlock,
6
- GroupContent,
7
6
  ItemDeclaration,
8
7
  LabelDeclaration,
9
8
  MilestoneDeclaration,
9
+ NonWorkingDeclaration,
10
10
  NowlineFile,
11
11
  ParallelBlock,
12
- ParallelContent,
13
12
  PersonDeclaration,
14
13
  SizeDeclaration,
15
14
  StatusDeclaration,
16
15
  StyleDeclaration,
17
- SwimlaneContent,
18
16
  SwimlaneDeclaration,
19
17
  SymbolDeclaration,
20
18
  TeamDeclaration,
19
+ WaveDeclaration,
20
+ WorkingDeclaration,
21
21
  } from '@nowline/core';
22
22
  import {
23
23
  isAnchorDeclaration,
@@ -26,19 +26,22 @@ import {
26
26
  isItemDeclaration,
27
27
  isLabelDeclaration,
28
28
  isMilestoneDeclaration,
29
+ isNonWorkingDeclaration,
29
30
  isNowlineFile,
30
31
  isParallelBlock,
31
32
  isPersonDeclaration,
32
- isPersonMemberRef,
33
33
  isSizeDeclaration,
34
34
  isStatusDeclaration,
35
35
  isStyleDeclaration,
36
36
  isSwimlaneDeclaration,
37
37
  isSymbolDeclaration,
38
38
  isTeamDeclaration,
39
+ isWaveDeclaration,
40
+ isWorkingDeclaration,
39
41
  } from '@nowline/core';
42
+ import { declarations, propKey, visitProperties } from '@nowline/core/internal';
40
43
  import type { AstNode, CstNode, LangiumDocument } from 'langium';
41
- import { CstUtils } from 'langium';
44
+ import { CstUtils, GrammarUtils } from 'langium';
42
45
  import type { Range } from '../lsp-protocol.js';
43
46
 
44
47
  /**
@@ -49,7 +52,7 @@ import type { Range } from '../lsp-protocol.js';
49
52
  * Wider than the validator's `checkReferenceResolution` (which is strict only
50
53
  * for sequencing — `after` / `before` / `on`). Categorisation references
51
54
  * (`size` / `status` / `style` / `labels`), decoration references
52
- * (`icon` / `capacity-icon`), and ownership references (`owner` / `team`) are
55
+ * (`icon` / `capacity-icon`), and the ownership reference (`owner`) are
53
56
  * permissive in the validator (an undeclared id is not an error) but still
54
57
  * navigable when a declaration exists.
55
58
  *
@@ -57,60 +60,44 @@ import type { Range } from '../lsp-protocol.js';
57
60
  * form was removed in favor of the spec-mandated reverse direction.
58
61
  * Footnote association flows only from the footnote declaration's `on:`,
59
62
  * and the validator now rejects `footnote:` as a property on host entities.
63
+ *
64
+ * Every key here must be one the validator accepts; `reference-keys.test.ts`
65
+ * checks that.
60
66
  */
61
67
  export const REFERENCE_PROP_KEYS = new Set([
62
68
  'after',
63
69
  'before',
64
- 'depends',
65
70
  'on',
66
71
  'owner',
67
- 'team',
68
72
  'size',
69
73
  'status',
70
74
  'style',
71
75
  'labels',
72
76
  'icon',
73
77
  'capacity-icon',
78
+ 'wave',
74
79
  ]);
75
80
 
76
- /** Status value-completion source: built-in statuses ship with the renderer. */
77
- export const BUILTIN_STATUSES: readonly string[] = [
78
- 'planned',
79
- 'in-progress',
80
- 'done',
81
- 'at-risk',
82
- 'blocked',
83
- ];
84
-
85
- /**
86
- * Built-in `capacity-icon:` vocabulary. Mirrors `BUILTIN_CAPACITY_ICONS` in
87
- * the validator — kept as a literal here so the LSP doesn't have to import a
88
- * non-public symbol from `@nowline/core`.
89
- */
90
- export const BUILTIN_CAPACITY_ICONS: readonly string[] = [
91
- 'none',
92
- 'multiplier',
93
- 'person',
94
- 'people',
95
- 'points',
96
- 'time',
97
- ];
98
-
99
81
  /**
100
- * Built-in `icon:` vocabulary. Superset of `BUILTIN_CAPACITY_ICONS` plus the
101
- * entity-decoration glyphs the renderer ships.
82
+ * Value-completion sources. The vocabularies are core's (`value-tables.ts`):
83
+ * the validator checks values against the same lists, so the editor offers
84
+ * exactly what validates. Kept as `@nowline/lsp` exports. `WEEKDAY_NAMES` is
85
+ * the weekday names `every:` takes, Sunday first.
102
86
  */
103
- export const BUILTIN_ICON_NAMES: readonly string[] = [
104
- ...BUILTIN_CAPACITY_ICONS,
105
- 'shield',
106
- 'warning',
107
- 'lock',
108
- ];
87
+ export {
88
+ BUILTIN_CAPACITY_ICONS,
89
+ BUILTIN_ICON_NAMES,
90
+ BUILTIN_STATUSES,
91
+ CALENDAR_MODES,
92
+ COLOR_NAMES,
93
+ INCLUDE_MODES,
94
+ NON_WORKING_DISPLAYS,
95
+ STYLE_PROP_ENUMS,
96
+ WEEKDAYS as WEEKDAY_NAMES,
97
+ } from '@nowline/core/internal';
109
98
 
110
- /** Strip the trailing `:` an `EntityProperty.key` carries from the grammar. */
111
- export function propKey(prop: { key: string }): string {
112
- return prop.key.endsWith(':') ? prop.key.slice(0, -1) : prop.key;
113
- }
99
+ /** `prop.key` without a trailing `:` (core's, kept as an `@nowline/lsp` export). */
100
+ export { propKey };
114
101
 
115
102
  /**
116
103
  * Named declaration shapes we expose for navigation. All have `name?: string`,
@@ -119,6 +106,11 @@ export function propKey(prop: { key: string }): string {
119
106
  * Includes both roadmap-section declarations (item, swimlane, person, …) and
120
107
  * config-section declarations (`style`, `symbol`) so cmd+click on
121
108
  * `style:enterprise-style` and `icon:my-symbol` resolves to the right place.
109
+ *
110
+ * `non-working` and `working` declarations are here so `declarationAt` (hover)
111
+ * sees them, but `collectNamedEntities` skips them: nothing references a
112
+ * calendar declaration by id, so they stay out of reference completion,
113
+ * definition and rename.
122
114
  */
123
115
  export type NamedEntity =
124
116
  | RoadmapEntryNamed
@@ -126,7 +118,9 @@ export type NamedEntity =
126
118
  | ParallelBlock
127
119
  | GroupBlock
128
120
  | StyleDeclaration
129
- | SymbolDeclaration;
121
+ | SymbolDeclaration
122
+ | NonWorkingDeclaration
123
+ | WorkingDeclaration;
130
124
 
131
125
  type RoadmapEntryNamed =
132
126
  | SwimlaneDeclaration
@@ -137,64 +131,33 @@ type RoadmapEntryNamed =
137
131
  | TeamDeclaration
138
132
  | LabelDeclaration
139
133
  | SizeDeclaration
140
- | StatusDeclaration;
134
+ | StatusDeclaration
135
+ | WaveDeclaration;
141
136
 
142
137
  /**
143
- * Walk the `NowlineFile` and collect every entity that owns an `id`. Covers
144
- * roadmap-section entities (mirrors the validator's `collectReferenceableIds`)
145
- * plus the named config-section declarations (`style`, `symbol`) so style and
146
- * symbol references navigate correctly. Anonymous config blocks (`scale`,
147
- * `calendar`, `default <entity>`) are skipped.
138
+ * True for a `non-working` or `working` declaration. Hover describes them, but
139
+ * nothing references a calendar declaration by id, so definition, references
140
+ * and rename leave them alone.
141
+ */
142
+ export function isCalendarDeclaration(entity: AstNode): boolean {
143
+ return isNonWorkingDeclaration(entity) || isWorkingDeclaration(entity);
144
+ }
145
+
146
+ /**
147
+ * Collect every entity that owns an `id`, in the order of core's declaration
148
+ * walk (config entries first, then roadmap entries with their track content or
149
+ * nested teams): the roadmap-section entities `@nowline/core`'s
150
+ * `referenceableIds` covers, plus the named config-section declarations
151
+ * (`style`, `symbol`) so style and symbol references navigate correctly.
152
+ * The LSP skips the roadmap line itself (not walked here) and the calendar
153
+ * declarations (`isCalendarDeclaration`); anonymous declarations have no id.
148
154
  */
149
155
  export function collectNamedEntities(file: NowlineFile): NamedEntity[] {
150
156
  const out: NamedEntity[] = [];
151
-
152
- for (const entry of file.configEntries) {
153
- if (isStyleDeclaration(entry) && entry.name) out.push(entry);
154
- else if (isSymbolDeclaration(entry) && entry.name) out.push(entry);
157
+ for (const node of declarations(file, { config: true })) {
158
+ if (!(node as { name?: string }).name || isCalendarDeclaration(node)) continue;
159
+ out.push(node as NamedEntity);
155
160
  }
156
-
157
- const visitTeam = (team: TeamDeclaration) => {
158
- if (team.name) out.push(team);
159
- for (const c of team.content) {
160
- if (isTeamDeclaration(c)) visitTeam(c);
161
- // `PersonMemberRef` and `DescriptionDirective` are references / prose,
162
- // not declarations — skip.
163
- else if (isPersonMemberRef(c)) {
164
- /* skip */
165
- }
166
- }
167
- };
168
-
169
- const visitTrackChild = (child: SwimlaneContent | GroupContent | ParallelContent) => {
170
- if (isItemDeclaration(child) && child.name) out.push(child);
171
- else if (isParallelBlock(child)) {
172
- if (child.name) out.push(child);
173
- for (const sub of child.content) visitTrackChild(sub);
174
- } else if (isGroupBlock(child)) {
175
- if (child.name) out.push(child);
176
- for (const sub of child.content) visitTrackChild(sub);
177
- }
178
- };
179
-
180
- for (const entry of file.roadmapEntries) {
181
- if (isSwimlaneDeclaration(entry)) {
182
- if (entry.name) out.push(entry);
183
- for (const c of entry.content) visitTrackChild(c);
184
- } else if (isTeamDeclaration(entry)) visitTeam(entry);
185
- else if (
186
- isPersonDeclaration(entry) ||
187
- isAnchorDeclaration(entry) ||
188
- isMilestoneDeclaration(entry) ||
189
- isFootnoteDeclaration(entry) ||
190
- isLabelDeclaration(entry) ||
191
- isSizeDeclaration(entry) ||
192
- isStatusDeclaration(entry)
193
- ) {
194
- if (entry.name) out.push(entry);
195
- }
196
- }
197
-
198
161
  return out;
199
162
  }
200
163
 
@@ -213,27 +176,7 @@ export function buildEntityIndex(file: NowlineFile): Map<string, NamedEntity> {
213
176
  }
214
177
 
215
178
  /** Visit every `EntityProperty` reachable from a roadmap entry, depth-first. */
216
- export function visitProperties(
217
- node: AstNode,
218
- visit: (prop: EntityProperty, owner: AstNode) => void,
219
- ): void {
220
- const walk = (n: AstNode): void => {
221
- const props = (n as { properties?: EntityProperty[] }).properties;
222
- if (Array.isArray(props)) {
223
- for (const p of props) visit(p, n);
224
- }
225
- if (isSwimlaneDeclaration(n)) {
226
- for (const c of n.content) walk(c);
227
- } else if (isParallelBlock(n) || isGroupBlock(n)) {
228
- for (const c of n.content) walk(c);
229
- } else if (isTeamDeclaration(n)) {
230
- for (const c of n.content) {
231
- if (isTeamDeclaration(c) || isPersonDeclaration(c)) walk(c);
232
- }
233
- }
234
- };
235
- walk(node);
236
- }
179
+ export { visitProperties };
237
180
 
238
181
  /**
239
182
  * Iterate every `EntityProperty` in the file, including those on the roadmap
@@ -297,13 +240,16 @@ export function propertyValueAt(
297
240
 
298
241
  /**
299
242
  * When `leaf` is the `name=ID` token of a named entity declaration, return the
300
- * declaring entity. Returns `undefined` for any other position.
243
+ * declaring entity. Returns `undefined` for any other position, including the
244
+ * declaration keyword (`wave wave "W"` names its wave after the keyword).
301
245
  */
302
246
  export function declarationAt(leaf: CstNode | undefined): NamedEntity | undefined {
303
247
  if (!leaf) return undefined;
304
248
  const owner = leaf.astNode as AstNode & { name?: string };
305
249
  if (!owner || typeof owner.name !== 'string') return undefined;
306
250
  if (owner.name !== leaf.text) return undefined;
251
+ const nameNode = nameNodeOf(owner);
252
+ if (!nameNode || leaf.offset < nameNode.offset || leaf.end > nameNode.end) return undefined;
307
253
  if (
308
254
  isSwimlaneDeclaration(owner) ||
309
255
  isItemDeclaration(owner) ||
@@ -317,8 +263,11 @@ export function declarationAt(leaf: CstNode | undefined): NamedEntity | undefine
317
263
  isLabelDeclaration(owner) ||
318
264
  isSizeDeclaration(owner) ||
319
265
  isStatusDeclaration(owner) ||
266
+ isWaveDeclaration(owner) ||
320
267
  isStyleDeclaration(owner) ||
321
- isSymbolDeclaration(owner)
268
+ isSymbolDeclaration(owner) ||
269
+ isNonWorkingDeclaration(owner) ||
270
+ isWorkingDeclaration(owner)
322
271
  ) {
323
272
  return owner as NamedEntity;
324
273
  }
@@ -348,6 +297,8 @@ export function findDeclarationRange(file: NowlineFile, id: string): Range | und
348
297
  * Render a one-line "type id" label used by hover and document-symbols.
349
298
  */
350
299
  export function entityKind(entity: AstNode): string {
300
+ // `NonWorkingDeclaration` would lowercase to `nonworking`; the keyword is hyphenated.
301
+ if (entity.$type === 'NonWorkingDeclaration') return 'non-working';
351
302
  return entity.$type
352
303
  .replace(/Declaration$/, '')
353
304
  .replace(/Block$/, '')
@@ -355,18 +306,21 @@ export function entityKind(entity: AstNode): string {
355
306
  }
356
307
 
357
308
  /**
358
- * Find the CST node that holds the given entity's `name=ID` token. Walks the
359
- * entity's CST subtree and returns the first leaf whose text equals the
360
- * entity's resolved name. Returns `undefined` when the entity has no `name`
361
- * (anonymous declaration), no CST node, or no matching leaf.
309
+ * Find the range of the given entity's `name=ID` token. Uses the grammar
310
+ * assignment rather than matching leaf text, so a name that equals the
311
+ * declaration keyword (`wave wave "W"`) resolves to the name, not the
312
+ * keyword. Returns `undefined` when the entity has no `name` (anonymous
313
+ * declaration) or no CST node.
362
314
  */
363
315
  export function nameRangeOf(entity: AstNode): Range | undefined {
316
+ return nameNodeOf(entity)?.range;
317
+ }
318
+
319
+ // The CST node assigned to the entity's `name` property.
320
+ function nameNodeOf(entity: AstNode): CstNode | undefined {
364
321
  const name = (entity as { name?: string }).name;
365
322
  if (!name || !entity.$cstNode) return undefined;
366
- for (const leaf of CstUtils.flattenCst(entity.$cstNode)) {
367
- if (leaf.text === name) return leaf.range;
368
- }
369
- return undefined;
323
+ return GrammarUtils.findNodeForProperty(entity.$cstNode, 'name');
370
324
  }
371
325
 
372
326
  /**