@tsuzuri-lab/sed-language-server 0.3.0 → 0.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsuzuri-lab/sed-language-server",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Language server for POSIX sed with opt-in GNU syntax support.",
5
5
  "keywords": [
6
6
  "sed",
package/src/definition.js CHANGED
@@ -1,39 +1,30 @@
1
1
  import { rangeForNode, syntaxTreeFor } from "./syntax.js";
2
2
 
3
- function collectLabels(rootNode, offset) {
4
- const definitions = [];
5
- let reference;
6
- const stack = [rootNode];
7
-
8
- while (stack.length > 0) {
9
- const node = stack.pop();
10
- if (node.type === "label_definition") {
11
- definitions.push(node);
12
- } else if (
3
+ function labelReferenceAt(rootNode, offset) {
4
+ let node = rootNode.namedDescendantForIndex(offset);
5
+ while (node !== null) {
6
+ if (
13
7
  node.type === "label_reference" &&
14
8
  node.startIndex <= offset &&
15
9
  offset < node.endIndex
16
10
  ) {
17
- reference = node;
11
+ return node;
18
12
  }
19
- stack.push(...node.children.toReversed());
13
+ node = node.parent;
20
14
  }
21
-
22
- return { definitions, reference };
15
+ return undefined;
23
16
  }
24
17
 
25
18
  export function createDefinitionLocations(document, position, dialect) {
26
- const tree = syntaxTreeFor(document, dialect);
27
- const { definitions, reference } = collectLabels(
28
- tree.rootNode,
29
- document.offsetAt(position),
30
- );
19
+ const rootNode = syntaxTreeFor(document, dialect).rootNode;
20
+ const reference = labelReferenceAt(rootNode, document.offsetAt(position));
31
21
 
32
22
  if (reference === undefined) {
33
23
  return null;
34
24
  }
35
25
 
36
- const locations = definitions
26
+ const locations = rootNode
27
+ .descendantsOfType("label_definition")
37
28
  .filter((definition) => definition.text === reference.text)
38
29
  .map((definition) => ({
39
30
  uri: document.uri,
@@ -40,6 +40,10 @@ const issueDescriptions = {
40
40
  code: "invalid-substitution-flag",
41
41
  message: `Unknown ${dialectName(dialect)} sed substitution flag: \`${node.text}\`.`,
42
42
  }),
43
+ invalid_flags: (node, dialect) => ({
44
+ code: "invalid-substitution-flag",
45
+ message: `The ${dialectName(dialect)} sed substitution flags \`${node.text}\` contain invalid characters.`,
46
+ }),
43
47
  unexpected_text: (_node, dialect) => ({
44
48
  code: "unexpected-text",
45
49
  message: `Unexpected text after this ${dialectName(dialect)} sed command.`,
@@ -71,7 +75,8 @@ function descriptionFor(node, dialect) {
71
75
  message: `Invalid ${dialectName(dialect)} sed syntax.`,
72
76
  };
73
77
  }
74
- function issuePriority(node) {
78
+
79
+ function issueSpecificity(node) {
75
80
  if (node.isError) {
76
81
  return 0;
77
82
  }
@@ -81,6 +86,10 @@ function issuePriority(node) {
81
86
  return 2;
82
87
  }
83
88
 
89
+ function compareIssueRanges(left, right) {
90
+ return left.startIndex - right.startIndex || left.endIndex - right.endIndex;
91
+ }
92
+
84
93
  function preferSpecificIssues(nodes) {
85
94
  const issuesByRange = new Map();
86
95
 
@@ -89,31 +98,92 @@ function preferSpecificIssues(nodes) {
89
98
  const existing = issuesByRange.get(key);
90
99
  if (
91
100
  existing === undefined ||
92
- issuePriority(node) > issuePriority(existing)
101
+ issueSpecificity(node) > issueSpecificity(existing)
93
102
  ) {
94
103
  issuesByRange.set(key, node);
95
104
  }
96
105
  }
97
106
 
98
- return [...issuesByRange.values()].sort(
99
- (left, right) =>
100
- left.startIndex - right.startIndex || left.endIndex - right.endIndex,
101
- );
107
+ return [...issuesByRange.values()].sort(compareIssueRanges);
108
+ }
109
+
110
+ function collapseInvalidFlags(nodes) {
111
+ const groupedByParent = new Map();
112
+ const issues = [];
113
+
114
+ for (const node of nodes) {
115
+ const parent = node.parent;
116
+ if (node.type !== "invalid_flag" || parent?.type !== "substitute_flags") {
117
+ issues.push(node);
118
+ continue;
119
+ }
120
+
121
+ const key = `${parent.startIndex}:${parent.endIndex}`;
122
+ const group = groupedByParent.get(key);
123
+ if (group === undefined) {
124
+ groupedByParent.set(key, [node]);
125
+ } else {
126
+ group.push(node);
127
+ }
128
+ }
129
+
130
+ for (const invalidFlags of groupedByParent.values()) {
131
+ if (invalidFlags.length === 1) {
132
+ issues.push(invalidFlags[0]);
133
+ continue;
134
+ }
135
+
136
+ const first = invalidFlags[0];
137
+ const last = invalidFlags.at(-1);
138
+ issues.push({
139
+ type: "invalid_flags",
140
+ startIndex: first.startIndex,
141
+ endIndex: last.endIndex,
142
+ text: first.parent.text,
143
+ });
144
+ }
145
+
146
+ return issues.sort(compareIssueRanges);
147
+ }
148
+
149
+ function mergeAdjacentIssues(nodes) {
150
+ const merged = [];
151
+
152
+ for (const node of nodes) {
153
+ const previous = merged.at(-1);
154
+ if (previous === undefined || node.startIndex > previous.endIndex) {
155
+ merged.push(node);
156
+ continue;
157
+ }
158
+
159
+ merged[merged.length - 1] = {
160
+ type: previous.type,
161
+ text: previous.text,
162
+ isMissing: previous.isMissing,
163
+ startIndex: previous.startIndex,
164
+ endIndex: Math.max(previous.endIndex, node.endIndex),
165
+ };
166
+ }
167
+
168
+ return merged;
169
+ }
170
+
171
+ function normalizeIssues(nodes) {
172
+ return mergeAdjacentIssues(collapseInvalidFlags(preferSpecificIssues(nodes)));
102
173
  }
103
174
 
104
175
  export function createDiagnostics(document, dialect) {
105
- const tree = syntaxTreeFor(document, dialect);
106
-
107
- return preferSpecificIssues(collectSyntaxIssueNodes(tree.rootNode)).map(
108
- (node) => {
109
- const { code, message } = descriptionFor(node, dialect);
110
- return {
111
- severity: DiagnosticSeverity.Error,
112
- range: rangeForNode(document, node),
113
- message,
114
- code,
115
- source: diagnosticSource,
116
- };
117
- },
118
- );
176
+ const rootNode = syntaxTreeFor(document, dialect).rootNode;
177
+ const issueNodes = normalizeIssues(collectSyntaxIssueNodes(rootNode));
178
+
179
+ return issueNodes.map((node) => {
180
+ const { code, message } = descriptionFor(node, dialect);
181
+ return {
182
+ severity: DiagnosticSeverity.Error,
183
+ range: rangeForNode(document, node),
184
+ message,
185
+ code,
186
+ source: diagnosticSource,
187
+ };
188
+ });
119
189
  }
package/src/formatting.js CHANGED
@@ -75,12 +75,15 @@ function formatScript(rootNode, source, options) {
75
75
 
76
76
  export function createFormattingEdits(document, dialect, options) {
77
77
  const source = document.getText();
78
- const tree = syntaxTreeFor(document, dialect);
79
- if (collectSyntaxIssueNodes(tree.rootNode).length > 0) {
78
+ const rootNode = syntaxTreeFor(document, dialect).rootNode;
79
+ if (rootNode.hasError || collectSyntaxIssueNodes(rootNode).length > 0) {
80
80
  return [];
81
81
  }
82
82
 
83
- const formatted = formatScript(tree.rootNode, source, options);
83
+ const formatted = formatScript(rootNode, source, options);
84
+ if (!source.startsWith("#n") && formatted.startsWith("#n")) {
85
+ return [];
86
+ }
84
87
  if (formatted === source) {
85
88
  return [];
86
89
  }
package/src/server.js CHANGED
@@ -3,10 +3,8 @@
3
3
  import {
4
4
  createConnection,
5
5
  ErrorCodes,
6
- MessageType,
7
6
  ProposedFeatures,
8
7
  ResponseError,
9
- ShowMessageNotification,
10
8
  TextDocumentSyncKind,
11
9
  TextDocuments,
12
10
  } from "vscode-languageserver/node";
@@ -46,19 +44,6 @@ function resolveDialect(options) {
46
44
  return { dialect };
47
45
  }
48
46
 
49
- function configurationOptions(settings) {
50
- if (
51
- settings !== null &&
52
- typeof settings === "object" &&
53
- !Array.isArray(settings) &&
54
- Object.hasOwn(settings, "sedLanguageServer")
55
- ) {
56
- return settings.sedLanguageServer;
57
- }
58
-
59
- return settings;
60
- }
61
-
62
47
  function publishDiagnostics(document) {
63
48
  return connection.sendDiagnostics({
64
49
  uri: document.uri,
@@ -85,22 +70,6 @@ connection.onInitialize(({ initializationOptions }) => {
85
70
  };
86
71
  });
87
72
 
88
- connection.onDidChangeConfiguration(async ({ settings }) => {
89
- const result = resolveDialect(configurationOptions(settings));
90
- if (result.error !== undefined) {
91
- connection.sendNotification(ShowMessageNotification.type, {
92
- type: MessageType.Error,
93
- message: result.error,
94
- });
95
- return;
96
- }
97
-
98
- activeDialect = result.dialect;
99
- await Promise.all(
100
- documents.all().map((document) => publishDiagnostics(document)),
101
- );
102
- });
103
-
104
73
  connection.onDefinition(({ textDocument, position }) => {
105
74
  const document = documents.get(textDocument.uri);
106
75
  return document === undefined
package/src/syntax.js CHANGED
@@ -98,7 +98,7 @@ export function collectSyntaxIssueNodes(rootNode) {
98
98
 
99
99
  const specific = isSpecificSyntaxIssue(frame.node);
100
100
  const selected =
101
- specific || (frame.node.isError && !frame.hasDescendantIssue);
101
+ specific || (frame.node.hasError && !frame.hasDescendantIssue);
102
102
  if (specific) {
103
103
  issues.length = frame.issueStartIndex;
104
104
  }