@sarj/eslint-plugin 16.35.0 → 16.36.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.
@@ -125,6 +125,9 @@ async function verifyExample(linter, config, example, root, ruleId) {
125
125
  if (messages.length !== example.expectedCount) {
126
126
  throw new Error(`${example.id}: expected ${example.expectedCount} findings, received ${messages.length}`);
127
127
  }
128
+ if (example.expectedDiagnostics !== void 0) {
129
+ verifyExpectedDiagnostics(example, messages, focus.source, lines);
130
+ }
128
131
  for (const expected of example.fixedFiles ?? []) {
129
132
  const input = example.files.find((file) => file.path === expected.path);
130
133
  if (input === void 0) throw new Error(`${example.id}: fixed file has no original`);
@@ -138,6 +141,52 @@ async function verifyExample(linter, config, example, root, ruleId) {
138
141
  }
139
142
  }
140
143
  }
144
+ function verifyExpectedDiagnostics(example, messages, source, lines) {
145
+ const expectations = example.expectedDiagnostics ?? [];
146
+ if (expectations.some((item) => !physicalExpectedLocation(item.location, lines))) {
147
+ throw new Error(`${example.id}: invalid expected diagnostic location`);
148
+ }
149
+ const expected = expectations.map(expectedDiagnosticKey).sort();
150
+ const actual = messages.map((message) => JSON.stringify([
151
+ message.messageId,
152
+ message.message,
153
+ [message.line, message.column, message.endLine, message.endColumn],
154
+ message.fix !== void 0,
155
+ (message.suggestions ?? []).map((suggestion) => [
156
+ suggestion.messageId,
157
+ applySuggestion(source, suggestion.fix, example.id)
158
+ ])
159
+ ])).sort();
160
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
161
+ throw new Error(`${example.id}: unexpected diagnostics
162
+ expected: ${JSON.stringify(expected)}
163
+ received: ${JSON.stringify(actual)}`);
164
+ }
165
+ }
166
+ function expectedDiagnosticKey(expected) {
167
+ return JSON.stringify([
168
+ expected.messageId,
169
+ expected.message,
170
+ [expected.location.line, expected.location.column, expected.location.end?.line, expected.location.end?.column],
171
+ expected.automaticFix,
172
+ expected.suggestions.map((suggestion) => [suggestion.messageId, suggestion.output])
173
+ ]);
174
+ }
175
+ function applySuggestion(source, fix, exampleId) {
176
+ const bom = source.startsWith("\uFEFF") ? "\uFEFF" : "";
177
+ const text = bom === "" ? source : source.slice(1);
178
+ const [start, end] = fix.range;
179
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start > end || start < (bom === "" ? 0 : -1) || end < 0 || end > text.length) {
180
+ throw new Error(`${exampleId}: invalid suggestion edit range`);
181
+ }
182
+ const prefix = start < 0 && end >= 0 || start === 0 && fix.text.startsWith("\uFEFF") ? "" : bom;
183
+ return prefix + text.slice(0, Math.max(0, start)) + fix.text + text.slice(end);
184
+ }
185
+ function physicalExpectedLocation(location, lines) {
186
+ if (!physicalPosition(lines, location.line, location.column)) return false;
187
+ if (location.end === void 0) return true;
188
+ return physicalPosition(lines, location.end.line, location.end.column) && (location.end.line > location.line || location.end.line === location.line && location.end.column >= location.column);
189
+ }
141
190
  function physicalDiagnosticLocation(message, lines) {
142
191
  if (!physicalPosition(lines, message.line, message.column)) return false;
143
192
  if (message.endLine === void 0 && message.endColumn === void 0) return true;
@@ -91,6 +91,9 @@ async function verifyExample(linter, config, example, root, ruleId) {
91
91
  if (messages.length !== example.expectedCount) {
92
92
  throw new Error(`${example.id}: expected ${example.expectedCount} findings, received ${messages.length}`);
93
93
  }
94
+ if (example.expectedDiagnostics !== void 0) {
95
+ verifyExpectedDiagnostics(example, messages, focus.source, lines);
96
+ }
94
97
  for (const expected of example.fixedFiles ?? []) {
95
98
  const input = example.files.find((file) => file.path === expected.path);
96
99
  if (input === void 0) throw new Error(`${example.id}: fixed file has no original`);
@@ -104,6 +107,52 @@ async function verifyExample(linter, config, example, root, ruleId) {
104
107
  }
105
108
  }
106
109
  }
110
+ function verifyExpectedDiagnostics(example, messages, source, lines) {
111
+ const expectations = example.expectedDiagnostics ?? [];
112
+ if (expectations.some((item) => !physicalExpectedLocation(item.location, lines))) {
113
+ throw new Error(`${example.id}: invalid expected diagnostic location`);
114
+ }
115
+ const expected = expectations.map(expectedDiagnosticKey).sort();
116
+ const actual = messages.map((message) => JSON.stringify([
117
+ message.messageId,
118
+ message.message,
119
+ [message.line, message.column, message.endLine, message.endColumn],
120
+ message.fix !== void 0,
121
+ (message.suggestions ?? []).map((suggestion) => [
122
+ suggestion.messageId,
123
+ applySuggestion(source, suggestion.fix, example.id)
124
+ ])
125
+ ])).sort();
126
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
127
+ throw new Error(`${example.id}: unexpected diagnostics
128
+ expected: ${JSON.stringify(expected)}
129
+ received: ${JSON.stringify(actual)}`);
130
+ }
131
+ }
132
+ function expectedDiagnosticKey(expected) {
133
+ return JSON.stringify([
134
+ expected.messageId,
135
+ expected.message,
136
+ [expected.location.line, expected.location.column, expected.location.end?.line, expected.location.end?.column],
137
+ expected.automaticFix,
138
+ expected.suggestions.map((suggestion) => [suggestion.messageId, suggestion.output])
139
+ ]);
140
+ }
141
+ function applySuggestion(source, fix, exampleId) {
142
+ const bom = source.startsWith("\uFEFF") ? "\uFEFF" : "";
143
+ const text = bom === "" ? source : source.slice(1);
144
+ const [start, end] = fix.range;
145
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start > end || start < (bom === "" ? 0 : -1) || end < 0 || end > text.length) {
146
+ throw new Error(`${exampleId}: invalid suggestion edit range`);
147
+ }
148
+ const prefix = start < 0 && end >= 0 || start === 0 && fix.text.startsWith("\uFEFF") ? "" : bom;
149
+ return prefix + text.slice(0, Math.max(0, start)) + fix.text + text.slice(end);
150
+ }
151
+ function physicalExpectedLocation(location, lines) {
152
+ if (!physicalPosition(lines, location.line, location.column)) return false;
153
+ if (location.end === void 0) return true;
154
+ return physicalPosition(lines, location.end.line, location.end.column) && (location.end.line > location.line || location.end.line === location.line && location.end.column >= location.column);
155
+ }
107
156
  function physicalDiagnosticLocation(message, lines) {
108
157
  if (!physicalPosition(lines, message.line, message.column)) return false;
109
158
  if (message.endLine === void 0 && message.endColumn === void 0) return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.35.0",
3
+ "version": "16.36.0",
4
4
  "packageManager": "npm@12.2.1",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",