@parcel/diagnostic 2.0.0-beta.3 → 2.0.0-dev.1510

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.
@@ -0,0 +1,154 @@
1
+ import type { Mapping } from "@mischnic/json-sourcemap";
2
+ /** These positions are 1-based (so <code>1</code> is the first line/column) */
3
+ export type DiagnosticHighlightLocation = {
4
+ readonly line: number;
5
+ readonly column: number;
6
+ };
7
+ export type DiagnosticSeverity = "error" | "warn" | "info";
8
+ /**
9
+ * Note: A tab character is always counted as a single character
10
+ * This is to prevent any mismatch of highlighting across machines
11
+ */
12
+ export type DiagnosticCodeHighlight = {
13
+ /** Location of the first character that should get highlighted for this highlight. */
14
+ start: DiagnosticHighlightLocation;
15
+ /** Location of the last character that should get highlighted for this highlight. */
16
+ end: DiagnosticHighlightLocation;
17
+ /** A message that should be displayed at this location in the code (optional). */
18
+ message?: string;
19
+ };
20
+ /**
21
+ * Describes how to format a code frame.
22
+ * A code frame is a visualization of a piece of code with a certain amount of
23
+ * code highlights that point to certain chunk(s) inside the code.
24
+ */
25
+ export type DiagnosticCodeFrame = {
26
+ /**
27
+ * The contents of the source file.
28
+ *
29
+ * If no code is passed, it will be read in from filePath, remember that
30
+ * the asset's current code could be different from the input contents.
31
+ *
32
+ */
33
+ code?: string;
34
+ /** Path to the file this code frame is about (optional, absolute or relative to the project root) */
35
+ filePath?: string;
36
+ /** Language of the file this code frame is about (optional) */
37
+ language?: string;
38
+ codeHighlights: Array<DiagnosticCodeHighlight>;
39
+ };
40
+ /**
41
+ * A style agnostic way of emitting errors, warnings and info.
42
+ * Reporters are responsible for rendering the message, codeframes, hints, ...
43
+ */
44
+ export type Diagnostic = {
45
+ /** This is the message you want to log. */
46
+ message: string;
47
+ /** Name of plugin or file that threw this error */
48
+ origin?: string;
49
+ /** A stacktrace of the error (optional) */
50
+ stack?: string;
51
+ /** Name of the error (optional) */
52
+ name?: string;
53
+ /** A code frame points to a certain location(s) in the file this diagnostic is linked to (optional) */
54
+ codeFrames?: Array<DiagnosticCodeFrame> | null | undefined;
55
+ /** An optional list of strings that suggest ways to resolve this issue */
56
+ hints?: Array<string>;
57
+ /** @private */
58
+ skipFormatting?: boolean;
59
+ /** A URL to documentation to learn more about the diagnostic. */
60
+ documentationURL?: string;
61
+ };
62
+ export interface PrintableError extends Error {
63
+ fileName?: string;
64
+ filePath?: string;
65
+ codeFrame?: string;
66
+ highlightedCodeFrame?: string;
67
+ loc?: {
68
+ column: number;
69
+ line: number;
70
+ } | null | undefined;
71
+ source?: string;
72
+ }
73
+ export type DiagnosticWithoutOrigin = Diagnostic & {
74
+ origin?: string;
75
+ };
76
+ /** Something that can be turned into a diagnostic. */
77
+ export type Diagnostifiable = Diagnostic | Array<Diagnostic> | ThrowableDiagnostic | PrintableError | Error | string;
78
+ /** Normalize the given value into a diagnostic. */
79
+ export declare function anyToDiagnostic(input: Diagnostifiable): Array<Diagnostic>;
80
+ /** Normalize the given error into a diagnostic. */
81
+ export declare function errorToDiagnostic(error: ThrowableDiagnostic | PrintableError | string, defaultValues?: {
82
+ origin?: string | null | undefined;
83
+ filePath?: string | null | undefined;
84
+ }): Array<Diagnostic>;
85
+ type ThrowableDiagnosticOpts = {
86
+ diagnostic: Diagnostic | Array<Diagnostic>;
87
+ };
88
+ /**
89
+ * An error wrapper around a diagnostic that can be <code>throw</code>n (e.g. to signal a
90
+ * build error).
91
+ */
92
+ export default class ThrowableDiagnostic extends Error {
93
+ diagnostics: Array<Diagnostic>;
94
+ constructor(opts: ThrowableDiagnosticOpts);
95
+ }
96
+ /**
97
+ * Turns a list of positions in a JSON5 file with messages into a list of diagnostics.
98
+ * Uses <a href="https://github.com/mischnic/json-sourcemap">@mischnic/json-sourcemap</a>.
99
+ *
100
+ * @param code the JSON code
101
+ * @param ids A list of JSON keypaths (<code>key: "/some/parent/child"</code>) with corresponding messages, \
102
+ * <code>type</code> signifies whether the key of the value in a JSON object should be highlighted.
103
+ */
104
+ export declare function generateJSONCodeHighlights(data: string | {
105
+ data: unknown;
106
+ pointers: Record<string, Mapping>;
107
+ }, ids: Array<{
108
+ key: string;
109
+ type?: ("key" | null | undefined) | "value";
110
+ message?: string;
111
+ }>): Array<DiagnosticCodeHighlight>;
112
+ /**
113
+ * Converts entries in <a href="https://github.com/mischnic/json-sourcemap">@mischnic/json-sourcemap</a>'s
114
+ * <code>result.pointers</code> array.
115
+ */
116
+ export declare function getJSONHighlightLocation(pos: Mapping, type?: ("key" | null | undefined) | "value"): {
117
+ start: DiagnosticHighlightLocation;
118
+ end: DiagnosticHighlightLocation;
119
+ };
120
+ /** Result is 1-based, but end is exclusive */
121
+ export declare function getJSONSourceLocation(pos: Mapping, type?: ("key" | null | undefined) | "value"): {
122
+ start: {
123
+ readonly line: number;
124
+ readonly column: number;
125
+ };
126
+ end: {
127
+ readonly line: number;
128
+ readonly column: number;
129
+ };
130
+ };
131
+ export declare function convertSourceLocationToHighlight<Location extends {
132
+ /** 1-based, inclusive */
133
+ readonly start: {
134
+ readonly line: number;
135
+ readonly column: number;
136
+ };
137
+ /** 1-based, exclusive */
138
+ readonly end: {
139
+ readonly line: number;
140
+ readonly column: number;
141
+ };
142
+ }>({ start, end }: Location, message?: string): DiagnosticCodeHighlight;
143
+ /** Sanitizes object keys before using them as <code>key</code> in generateJSONCodeHighlights */
144
+ export declare function encodeJSONKeyComponent(component: string): string;
145
+ export declare function escapeMarkdown(s: string): string;
146
+ type TemplateInput = any;
147
+ export declare function md(strings: Array<string>, ...params: Array<TemplateInput>): string;
148
+ export declare namespace md {
149
+ var bold: (s: any) => any;
150
+ var italic: (s: any) => any;
151
+ var underline: (s: any) => any;
152
+ var strikethrough: (s: any) => any;
153
+ }
154
+ export {};
package/lib/diagnostic.js CHANGED
@@ -4,92 +4,96 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.anyToDiagnostic = anyToDiagnostic;
7
+ exports.convertSourceLocationToHighlight = convertSourceLocationToHighlight;
8
+ exports.default = void 0;
9
+ exports.encodeJSONKeyComponent = encodeJSONKeyComponent;
7
10
  exports.errorToDiagnostic = errorToDiagnostic;
11
+ exports.escapeMarkdown = escapeMarkdown;
8
12
  exports.generateJSONCodeHighlights = generateJSONCodeHighlights;
13
+ exports.getJSONHighlightLocation = getJSONHighlightLocation;
9
14
  exports.getJSONSourceLocation = getJSONSourceLocation;
10
- exports.encodeJSONKeyComponent = encodeJSONKeyComponent;
11
- exports.escapeMarkdown = escapeMarkdown;
12
15
  exports.md = md;
13
- exports.default = void 0;
14
-
15
16
  function _assert() {
16
17
  const data = _interopRequireDefault(require("assert"));
17
-
18
18
  _assert = function () {
19
19
  return data;
20
20
  };
21
-
22
21
  return data;
23
22
  }
24
-
25
23
  function _nullthrows() {
26
24
  const data = _interopRequireDefault(require("nullthrows"));
27
-
28
25
  _nullthrows = function () {
29
26
  return data;
30
27
  };
31
-
32
28
  return data;
33
29
  }
34
-
35
- function _jsonSourceMap() {
36
- const data = _interopRequireDefault(require("json-source-map"));
37
-
38
- _jsonSourceMap = function () {
30
+ function _jsonSourcemap() {
31
+ const data = require("@mischnic/json-sourcemap");
32
+ _jsonSourcemap = function () {
39
33
  return data;
40
34
  };
41
-
42
35
  return data;
43
36
  }
44
-
45
37
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
46
-
47
- function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
48
-
38
+ /** These positions are 1-based (so <code>1</code> is the first line/column) */
39
+ /**
40
+ * Note: A tab character is always counted as a single character
41
+ * This is to prevent any mismatch of highlighting across machines
42
+ */
43
+ /**
44
+ * Describes how to format a code frame.
45
+ * A code frame is a visualization of a piece of code with a certain amount of
46
+ * code highlights that point to certain chunk(s) inside the code.
47
+ */
48
+ /**
49
+ * A style agnostic way of emitting errors, warnings and info.
50
+ * Reporters are responsible for rendering the message, codeframes, hints, ...
51
+ */
52
+ // This type should represent all error formats Parcel can encounter...
53
+ /** Something that can be turned into a diagnostic. */
49
54
  /** Normalize the given value into a diagnostic. */
50
55
  function anyToDiagnostic(input) {
51
- // $FlowFixMe
52
- let diagnostic = input;
53
-
54
- if (input instanceof ThrowableDiagnostic) {
55
- diagnostic = input.diagnostics;
56
+ if (Array.isArray(input)) {
57
+ return input;
58
+ } else if (input instanceof ThrowableDiagnostic) {
59
+ return input.diagnostics;
56
60
  } else if (input instanceof Error) {
57
- diagnostic = errorToDiagnostic(input);
61
+ return errorToDiagnostic(input);
62
+ } else if (typeof input === 'string') {
63
+ return [{
64
+ message: input
65
+ }];
66
+ } else if (typeof input === 'object') {
67
+ return [input];
68
+ } else {
69
+ return errorToDiagnostic(input);
58
70
  }
59
-
60
- return Array.isArray(diagnostic) ? diagnostic : [diagnostic];
61
71
  }
62
- /** Normalize the given error into a diagnostic. */
63
-
64
-
65
- function errorToDiagnostic(error, defaultValues = { ...null
66
- }) {
67
- var _defaultValues$origin2, _ref2, _ref3, _error$filePath, _ref4, _error$highlightedCod;
68
-
69
- let codeFrame = undefined;
70
72
 
73
+ /** Normalize the given error into a diagnostic. */
74
+ function errorToDiagnostic(error, defaultValues) {
75
+ var _defaultValues$origin2, _ref4, _error$highlightedCod;
76
+ let codeFrames = undefined;
71
77
  if (typeof error === 'string') {
72
78
  var _defaultValues$origin;
73
-
74
79
  return [{
75
80
  origin: (_defaultValues$origin = defaultValues === null || defaultValues === void 0 ? void 0 : defaultValues.origin) !== null && _defaultValues$origin !== void 0 ? _defaultValues$origin : 'Error',
76
- message: escapeMarkdown(error),
77
- codeFrame
81
+ message: escapeMarkdown(error)
78
82
  }];
79
83
  }
80
-
81
84
  if (error instanceof ThrowableDiagnostic) {
82
85
  return error.diagnostics.map(d => {
83
86
  var _ref, _d$origin;
84
-
85
- return { ...d,
87
+ return {
88
+ ...d,
86
89
  origin: (_ref = (_d$origin = d.origin) !== null && _d$origin !== void 0 ? _d$origin : defaultValues === null || defaultValues === void 0 ? void 0 : defaultValues.origin) !== null && _ref !== void 0 ? _ref : 'unknown'
87
90
  };
88
91
  });
89
92
  }
90
-
91
93
  if (error.loc && error.source != null) {
92
- codeFrame = {
94
+ var _ref2, _ref3, _error$filePath;
95
+ codeFrames = [{
96
+ filePath: (_ref2 = (_ref3 = (_error$filePath = error.filePath) !== null && _error$filePath !== void 0 ? _error$filePath : error.fileName) !== null && _ref3 !== void 0 ? _ref3 : defaultValues === null || defaultValues === void 0 ? void 0 : defaultValues.filePath) !== null && _ref2 !== void 0 ? _ref2 : undefined,
93
97
  code: error.source,
94
98
  codeHighlights: [{
95
99
  start: {
@@ -101,19 +105,16 @@ function errorToDiagnostic(error, defaultValues = { ...null
101
105
  column: error.loc.column
102
106
  }
103
107
  }]
104
- };
108
+ }];
105
109
  }
106
-
107
110
  return [{
108
111
  origin: (_defaultValues$origin2 = defaultValues === null || defaultValues === void 0 ? void 0 : defaultValues.origin) !== null && _defaultValues$origin2 !== void 0 ? _defaultValues$origin2 : 'Error',
109
112
  message: escapeMarkdown(error.message),
110
113
  name: error.name,
111
- filePath: (_ref2 = (_ref3 = (_error$filePath = error.filePath) !== null && _error$filePath !== void 0 ? _error$filePath : error.fileName) !== null && _ref3 !== void 0 ? _ref3 : defaultValues === null || defaultValues === void 0 ? void 0 : defaultValues.filePath) !== null && _ref2 !== void 0 ? _ref2 : undefined,
112
- stack: (_ref4 = (_error$highlightedCod = error.highlightedCodeFrame) !== null && _error$highlightedCod !== void 0 ? _error$highlightedCod : error.codeFrame) !== null && _ref4 !== void 0 ? _ref4 : error.stack,
113
- codeFrame
114
+ stack: codeFrames == null ? (_ref4 = (_error$highlightedCod = error.highlightedCodeFrame) !== null && _error$highlightedCod !== void 0 ? _error$highlightedCod : error.codeFrame) !== null && _ref4 !== void 0 ? _ref4 : error.stack : undefined,
115
+ codeFrames
114
116
  }];
115
117
  }
116
-
117
118
  /**
118
119
  * An error wrapper around a diagnostic that can be <code>throw</code>n (e.g. to signal a
119
120
  * build error).
@@ -121,58 +122,58 @@ function errorToDiagnostic(error, defaultValues = { ...null
121
122
  class ThrowableDiagnostic extends Error {
122
123
  constructor(opts) {
123
124
  var _diagnostics$0$stack, _diagnostics$0$name;
125
+ let diagnostics = Array.isArray(opts.diagnostic) ? opts.diagnostic : [opts.diagnostic];
124
126
 
125
- let diagnostics = Array.isArray(opts.diagnostic) ? opts.diagnostic : [opts.diagnostic]; // Construct error from diagnostics
126
-
127
+ // Construct error from diagnostics
127
128
  super(diagnostics[0].message);
128
-
129
- _defineProperty(this, "diagnostics", void 0);
130
-
129
+ // @ts-ignore
131
130
  this.stack = (_diagnostics$0$stack = diagnostics[0].stack) !== null && _diagnostics$0$stack !== void 0 ? _diagnostics$0$stack : super.stack;
131
+ // @ts-ignore
132
132
  this.name = (_diagnostics$0$name = diagnostics[0].name) !== null && _diagnostics$0$name !== void 0 ? _diagnostics$0$name : super.name;
133
133
  this.diagnostics = diagnostics;
134
134
  }
135
-
136
135
  }
136
+
137
137
  /**
138
- * Turns a list of positions in a JSON file with messages into a list of diagnostics.
139
- * Uses <a href="https://github.com/epoberezkin/json-source-map">epoberezkin/json-source-map</a>.
138
+ * Turns a list of positions in a JSON5 file with messages into a list of diagnostics.
139
+ * Uses <a href="https://github.com/mischnic/json-sourcemap">@mischnic/json-sourcemap</a>.
140
140
  *
141
141
  * @param code the JSON code
142
142
  * @param ids A list of JSON keypaths (<code>key: "/some/parent/child"</code>) with corresponding messages, \
143
143
  * <code>type</code> signifies whether the key of the value in a JSON object should be highlighted.
144
144
  */
145
-
146
-
147
145
  exports.default = ThrowableDiagnostic;
148
-
149
146
  function generateJSONCodeHighlights(data, ids) {
150
- // json-source-map doesn't support a tabWidth option (yet)
151
- let map = typeof data == 'string' ? _jsonSourceMap().default.parse(data.replace(/\t/g, ' ')) : data;
147
+ let map = typeof data == 'string' ? (0, _jsonSourcemap().parse)(data, undefined, {
148
+ dialect: 'JSON5',
149
+ tabWidth: 1
150
+ }) : data;
152
151
  return ids.map(({
153
152
  key,
154
153
  type,
155
154
  message
156
155
  }) => {
157
156
  let pos = (0, _nullthrows().default)(map.pointers[key]);
158
- return { ...getJSONSourceLocation(pos, type),
157
+ return {
158
+ ...getJSONHighlightLocation(pos, type),
159
159
  message
160
160
  };
161
161
  });
162
162
  }
163
+
163
164
  /**
164
- * Converts entries in <a href="https://github.com/epoberezkin/json-source-map">epoberezkin/json-source-map</a>'s
165
+ * Converts entries in <a href="https://github.com/mischnic/json-sourcemap">@mischnic/json-sourcemap</a>'s
165
166
  * <code>result.pointers</code> array.
166
167
  */
167
-
168
-
169
- function getJSONSourceLocation(pos, type) {
170
- if (!type && pos.key && pos.value) {
168
+ function getJSONHighlightLocation(pos, type) {
169
+ let key = 'key' in pos ? pos.key : undefined;
170
+ let keyEnd = 'keyEnd' in pos ? pos.keyEnd : undefined;
171
+ if (!type && key && pos.value) {
171
172
  // key and value
172
173
  return {
173
174
  start: {
174
- line: pos.key.line + 1,
175
- column: pos.key.column + 1
175
+ line: key.line + 1,
176
+ column: key.column + 1
176
177
  },
177
178
  end: {
178
179
  line: pos.valueEnd.line + 1,
@@ -180,15 +181,15 @@ function getJSONSourceLocation(pos, type) {
180
181
  }
181
182
  };
182
183
  } else if (type == 'key' || !pos.value) {
183
- (0, _assert().default)(pos.key && pos.keyEnd);
184
+ (0, _assert().default)(key && keyEnd);
184
185
  return {
185
186
  start: {
186
- line: pos.key.line + 1,
187
- column: pos.key.column + 1
187
+ line: key.line + 1,
188
+ column: key.column + 1
188
189
  },
189
190
  end: {
190
- line: pos.keyEnd.line + 1,
191
- column: pos.keyEnd.column
191
+ line: keyEnd.line + 1,
192
+ column: keyEnd.column
192
193
  }
193
194
  };
194
195
  } else {
@@ -204,67 +205,86 @@ function getJSONSourceLocation(pos, type) {
204
205
  };
205
206
  }
206
207
  }
207
- /** Sanitizes object keys before using them as <code>key</code> in generateJSONCodeHighlights */
208
208
 
209
+ /** Result is 1-based, but end is exclusive */
210
+ function getJSONSourceLocation(pos, type) {
211
+ let v = getJSONHighlightLocation(pos, type);
212
+ return {
213
+ start: v.start,
214
+ end: {
215
+ line: v.end.line,
216
+ column: v.end.column + 1
217
+ }
218
+ };
219
+ }
220
+ function convertSourceLocationToHighlight({
221
+ start,
222
+ end
223
+ }, message) {
224
+ return {
225
+ message,
226
+ start,
227
+ end: {
228
+ line: end.line,
229
+ column: end.column - 1
230
+ }
231
+ };
232
+ }
209
233
 
234
+ /** Sanitizes object keys before using them as <code>key</code> in generateJSONCodeHighlights */
210
235
  function encodeJSONKeyComponent(component) {
211
- return component.replace(/\//g, '~1');
236
+ return component.replace(/~/g, '~0').replace(/\//g, '~1');
212
237
  }
213
-
214
238
  const escapeCharacters = ['\\', '*', '_', '~'];
215
-
216
239
  function escapeMarkdown(s) {
217
240
  let result = s;
218
-
219
241
  for (const char of escapeCharacters) {
220
242
  result = result.replace(new RegExp(`\\${char}`, 'g'), `\\${char}`);
221
243
  }
222
-
223
244
  return result;
224
- } // $FlowFixMe[unclear-type]
225
-
226
-
245
+ }
227
246
  const mdVerbatim = Symbol();
228
-
229
247
  function md(strings, ...params) {
230
248
  let result = [];
231
-
232
249
  for (let i = 0; i < params.length; i++) {
250
+ result.push(strings[i]);
233
251
  let param = params[i];
234
- result.push(strings[i], param !== null && param !== void 0 && param[mdVerbatim] ? param.value : escapeMarkdown(`${param}`));
252
+ if (Array.isArray(param)) {
253
+ for (let j = 0; j < param.length; j++) {
254
+ var _param$j$mdVerbatim, _param$j;
255
+ result.push((_param$j$mdVerbatim = (_param$j = param[j]) === null || _param$j === void 0 ? void 0 : _param$j[mdVerbatim]) !== null && _param$j$mdVerbatim !== void 0 ? _param$j$mdVerbatim : escapeMarkdown(`${param[j]}`));
256
+ if (j < param.length - 1) {
257
+ result.push(', ');
258
+ }
259
+ }
260
+ } else {
261
+ var _param$mdVerbatim;
262
+ result.push((_param$mdVerbatim = param === null || param === void 0 ? void 0 : param[mdVerbatim]) !== null && _param$mdVerbatim !== void 0 ? _param$mdVerbatim : escapeMarkdown(`${param}`));
263
+ }
235
264
  }
236
-
237
265
  return result.join('') + strings[strings.length - 1];
238
266
  }
239
-
240
267
  md.bold = function (s) {
241
268
  // $FlowFixMe[invalid-computed-prop]
242
269
  return {
243
- [mdVerbatim]: true,
244
- value: '**' + escapeMarkdown(`${s}`) + '**'
270
+ [mdVerbatim]: '**' + escapeMarkdown(`${s}`) + '**'
245
271
  };
246
272
  };
247
-
248
273
  md.italic = function (s) {
249
274
  // $FlowFixMe[invalid-computed-prop]
250
275
  return {
251
- [mdVerbatim]: true,
252
- value: '_' + escapeMarkdown(`${s}`) + '_'
276
+ [mdVerbatim]: '_' + escapeMarkdown(`${s}`) + '_'
253
277
  };
254
278
  };
255
-
256
279
  md.underline = function (s) {
257
280
  // $FlowFixMe[invalid-computed-prop]
258
281
  return {
259
- [mdVerbatim]: true,
260
- value: '__' + escapeMarkdown(`${s}`) + '__'
282
+ [mdVerbatim]: '__' + escapeMarkdown(`${s}`) + '__'
261
283
  };
262
284
  };
263
-
264
285
  md.strikethrough = function (s) {
265
286
  // $FlowFixMe[invalid-computed-prop]
266
287
  return {
267
- [mdVerbatim]: true,
268
- value: '~~' + escapeMarkdown(`${s}`) + '~~'
288
+ [mdVerbatim]: '~~' + escapeMarkdown(`${s}`) + '~~'
269
289
  };
270
290
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/diagnostic",
3
- "version": "2.0.0-beta.3",
3
+ "version": "2.0.0-dev.1510+a9bb85adf",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -15,12 +15,17 @@
15
15
  },
16
16
  "main": "lib/diagnostic.js",
17
17
  "source": "src/diagnostic.js",
18
+ "types": "lib/diagnostic.d.ts",
18
19
  "engines": {
19
20
  "node": ">= 12.0.0"
20
21
  },
22
+ "scripts": {
23
+ "build-ts": "flow-to-ts src/*.js --write && tsc --emitDeclarationOnly --declaration --esModuleInterop src/*.ts && mkdir -p lib && mv src/*.d.ts lib/. && rm src/*.ts",
24
+ "check-ts": "tsc --noEmit lib/diagnostic.d.ts"
25
+ },
21
26
  "dependencies": {
22
- "json-source-map": "^0.6.1",
27
+ "@mischnic/json-sourcemap": "^0.1.0",
23
28
  "nullthrows": "^1.1.1"
24
29
  },
25
- "gitHead": "bf03f018093f8c7ca4c3a544524e15065a637b3b"
30
+ "gitHead": "a9bb85adf8f3b38631e178b3aacaa30c78696e36"
26
31
  }
package/src/diagnostic.js CHANGED
@@ -1,9 +1,8 @@
1
1
  // @flow strict-local
2
- import type {FilePath} from '@parcel/types';
3
2
 
4
3
  import invariant from 'assert';
5
4
  import nullthrows from 'nullthrows';
6
- import jsonMap, {type Mapping} from 'json-source-map';
5
+ import {parse, type Mapping} from '@mischnic/json-sourcemap';
7
6
 
8
7
  /** These positions are 1-based (so <code>1</code> is the first line/column) */
9
8
  export type DiagnosticHighlightLocation = {|
@@ -35,11 +34,15 @@ export type DiagnosticCodeFrame = {|
35
34
  /**
36
35
  * The contents of the source file.
37
36
  *
38
- * If no code is passed, it will be read in from Diagnostic#filePath, remember that
37
+ * If no code is passed, it will be read in from filePath, remember that
39
38
  * the asset's current code could be different from the input contents.
40
39
  *
41
40
  */
42
41
  code?: string,
42
+ /** Path to the file this code frame is about (optional, absolute or relative to the project root) */
43
+ filePath?: string,
44
+ /** Language of the file this code frame is about (optional) */
45
+ language?: string,
43
46
  codeHighlights: Array<DiagnosticCodeHighlight>,
44
47
  |};
45
48
 
@@ -58,35 +61,32 @@ export type Diagnostic = {|
58
61
  /** Name of the error (optional) */
59
62
  name?: string,
60
63
 
61
- /** Path to the file this diagnostic is about (optional, absolute or relative to the project root) */
62
- filePath?: FilePath,
63
- /** Language of the file this diagnostic is about (optional) */
64
- language?: string,
65
-
66
64
  /** A code frame points to a certain location(s) in the file this diagnostic is linked to (optional) */
67
- codeFrame?: DiagnosticCodeFrame,
65
+ codeFrames?: ?Array<DiagnosticCodeFrame>,
68
66
 
69
67
  /** An optional list of strings that suggest ways to resolve this issue */
70
68
  hints?: Array<string>,
71
69
 
72
70
  /** @private */
73
71
  skipFormatting?: boolean,
72
+
73
+ /** A URL to documentation to learn more about the diagnostic. */
74
+ documentationURL?: string,
74
75
  |};
75
76
 
76
77
  // This type should represent all error formats Parcel can encounter...
77
- export type PrintableError = Error & {
78
- fileName?: string,
79
- filePath?: string,
80
- codeFrame?: string,
81
- highlightedCodeFrame?: string,
78
+ export interface PrintableError extends Error {
79
+ fileName?: string;
80
+ filePath?: string;
81
+ codeFrame?: string;
82
+ highlightedCodeFrame?: string;
82
83
  loc?: ?{
83
84
  column: number,
84
85
  line: number,
85
86
  ...
86
- },
87
- source?: string,
88
- ...
89
- };
87
+ };
88
+ source?: string;
89
+ }
90
90
 
91
91
  export type DiagnosticWithoutOrigin = {|
92
92
  ...Diagnostic,
@@ -99,38 +99,41 @@ export type Diagnostifiable =
99
99
  | Array<Diagnostic>
100
100
  | ThrowableDiagnostic
101
101
  | PrintableError
102
+ | Error
102
103
  | string;
103
104
 
104
105
  /** Normalize the given value into a diagnostic. */
105
106
  export function anyToDiagnostic(input: Diagnostifiable): Array<Diagnostic> {
106
- // $FlowFixMe
107
- let diagnostic: Array<Diagnostic> = input;
108
-
109
- if (input instanceof ThrowableDiagnostic) {
110
- diagnostic = input.diagnostics;
107
+ if (Array.isArray(input)) {
108
+ return input;
109
+ } else if (input instanceof ThrowableDiagnostic) {
110
+ return input.diagnostics;
111
111
  } else if (input instanceof Error) {
112
- diagnostic = errorToDiagnostic(input);
112
+ return errorToDiagnostic(input);
113
+ } else if (typeof input === 'string') {
114
+ return [{message: input}];
115
+ } else if (typeof input === 'object') {
116
+ return [input];
117
+ } else {
118
+ return errorToDiagnostic(input);
113
119
  }
114
-
115
- return Array.isArray(diagnostic) ? diagnostic : [diagnostic];
116
120
  }
117
121
 
118
122
  /** Normalize the given error into a diagnostic. */
119
123
  export function errorToDiagnostic(
120
124
  error: ThrowableDiagnostic | PrintableError | string,
121
- defaultValues: {|
125
+ defaultValues?: {|
122
126
  origin?: ?string,
123
127
  filePath?: ?string,
124
- |} = {...null},
128
+ |},
125
129
  ): Array<Diagnostic> {
126
- let codeFrame: DiagnosticCodeFrame | void = undefined;
130
+ let codeFrames: ?Array<DiagnosticCodeFrame> = undefined;
127
131
 
128
132
  if (typeof error === 'string') {
129
133
  return [
130
134
  {
131
135
  origin: defaultValues?.origin ?? 'Error',
132
136
  message: escapeMarkdown(error),
133
- codeFrame,
134
137
  },
135
138
  ];
136
139
  }
@@ -145,21 +148,28 @@ export function errorToDiagnostic(
145
148
  }
146
149
 
147
150
  if (error.loc && error.source != null) {
148
- codeFrame = {
149
- code: error.source,
150
- codeHighlights: [
151
- {
152
- start: {
153
- line: error.loc.line,
154
- column: error.loc.column,
155
- },
156
- end: {
157
- line: error.loc.line,
158
- column: error.loc.column,
151
+ codeFrames = [
152
+ {
153
+ filePath:
154
+ error.filePath ??
155
+ error.fileName ??
156
+ defaultValues?.filePath ??
157
+ undefined,
158
+ code: error.source,
159
+ codeHighlights: [
160
+ {
161
+ start: {
162
+ line: error.loc.line,
163
+ column: error.loc.column,
164
+ },
165
+ end: {
166
+ line: error.loc.line,
167
+ column: error.loc.column,
168
+ },
159
169
  },
160
- },
161
- ],
162
- };
170
+ ],
171
+ },
172
+ ];
163
173
  }
164
174
 
165
175
  return [
@@ -167,13 +177,11 @@ export function errorToDiagnostic(
167
177
  origin: defaultValues?.origin ?? 'Error',
168
178
  message: escapeMarkdown(error.message),
169
179
  name: error.name,
170
- filePath:
171
- error.filePath ??
172
- error.fileName ??
173
- defaultValues?.filePath ??
174
- undefined,
175
- stack: error.highlightedCodeFrame ?? error.codeFrame ?? error.stack,
176
- codeFrame,
180
+ stack:
181
+ codeFrames == null
182
+ ? error.highlightedCodeFrame ?? error.codeFrame ?? error.stack
183
+ : undefined,
184
+ codeFrames,
177
185
  },
178
186
  ];
179
187
  }
@@ -197,7 +205,9 @@ export default class ThrowableDiagnostic extends Error {
197
205
 
198
206
  // Construct error from diagnostics
199
207
  super(diagnostics[0].message);
208
+ // @ts-ignore
200
209
  this.stack = diagnostics[0].stack ?? super.stack;
210
+ // @ts-ignore
201
211
  this.name = diagnostics[0].name ?? super.name;
202
212
 
203
213
  this.diagnostics = diagnostics;
@@ -205,8 +215,8 @@ export default class ThrowableDiagnostic extends Error {
205
215
  }
206
216
 
207
217
  /**
208
- * Turns a list of positions in a JSON file with messages into a list of diagnostics.
209
- * Uses <a href="https://github.com/epoberezkin/json-source-map">epoberezkin/json-source-map</a>.
218
+ * Turns a list of positions in a JSON5 file with messages into a list of diagnostics.
219
+ * Uses <a href="https://github.com/mischnic/json-sourcemap">@mischnic/json-sourcemap</a>.
210
220
  *
211
221
  * @param code the JSON code
212
222
  * @param ids A list of JSON keypaths (<code>key: "/some/parent/child"</code>) with corresponding messages, \
@@ -221,40 +231,43 @@ export function generateJSONCodeHighlights(
221
231
  |},
222
232
  ids: Array<{|key: string, type?: ?'key' | 'value', message?: string|}>,
223
233
  ): Array<DiagnosticCodeHighlight> {
224
- // json-source-map doesn't support a tabWidth option (yet)
225
234
  let map =
226
- typeof data == 'string' ? jsonMap.parse(data.replace(/\t/g, ' ')) : data;
235
+ typeof data == 'string'
236
+ ? parse(data, undefined, {dialect: 'JSON5', tabWidth: 1})
237
+ : data;
227
238
  return ids.map(({key, type, message}) => {
228
239
  let pos = nullthrows(map.pointers[key]);
229
240
  return {
230
- ...getJSONSourceLocation(pos, type),
241
+ ...getJSONHighlightLocation(pos, type),
231
242
  message,
232
243
  };
233
244
  });
234
245
  }
235
246
 
236
247
  /**
237
- * Converts entries in <a href="https://github.com/epoberezkin/json-source-map">epoberezkin/json-source-map</a>'s
248
+ * Converts entries in <a href="https://github.com/mischnic/json-sourcemap">@mischnic/json-sourcemap</a>'s
238
249
  * <code>result.pointers</code> array.
239
250
  */
240
- export function getJSONSourceLocation(
251
+ export function getJSONHighlightLocation(
241
252
  pos: Mapping,
242
253
  type?: ?'key' | 'value',
243
254
  ): {|
244
255
  start: DiagnosticHighlightLocation,
245
256
  end: DiagnosticHighlightLocation,
246
257
  |} {
247
- if (!type && pos.key && pos.value) {
258
+ let key = 'key' in pos ? pos.key : undefined;
259
+ let keyEnd = 'keyEnd' in pos ? pos.keyEnd : undefined;
260
+ if (!type && key && pos.value) {
248
261
  // key and value
249
262
  return {
250
- start: {line: pos.key.line + 1, column: pos.key.column + 1},
263
+ start: {line: key.line + 1, column: key.column + 1},
251
264
  end: {line: pos.valueEnd.line + 1, column: pos.valueEnd.column},
252
265
  };
253
266
  } else if (type == 'key' || !pos.value) {
254
- invariant(pos.key && pos.keyEnd);
267
+ invariant(key && keyEnd);
255
268
  return {
256
- start: {line: pos.key.line + 1, column: pos.key.column + 1},
257
- end: {line: pos.keyEnd.line + 1, column: pos.keyEnd.column},
269
+ start: {line: key.line + 1, column: key.column + 1},
270
+ end: {line: keyEnd.line + 1, column: keyEnd.column},
258
271
  };
259
272
  } else {
260
273
  return {
@@ -264,9 +277,45 @@ export function getJSONSourceLocation(
264
277
  }
265
278
  }
266
279
 
280
+ /** Result is 1-based, but end is exclusive */
281
+ export function getJSONSourceLocation(
282
+ pos: Mapping,
283
+ type?: ?'key' | 'value',
284
+ ): {|
285
+ start: {|
286
+ +line: number,
287
+ +column: number,
288
+ |},
289
+ end: {|
290
+ +line: number,
291
+ +column: number,
292
+ |},
293
+ |} {
294
+ let v = getJSONHighlightLocation(pos, type);
295
+ return {start: v.start, end: {line: v.end.line, column: v.end.column + 1}};
296
+ }
297
+
298
+ export function convertSourceLocationToHighlight<
299
+ Location: {
300
+ /** 1-based, inclusive */
301
+ +start: {|
302
+ +line: number,
303
+ +column: number,
304
+ |},
305
+ /** 1-based, exclusive */
306
+ +end: {|
307
+ +line: number,
308
+ +column: number,
309
+ |},
310
+ ...
311
+ },
312
+ >({start, end}: Location, message?: string): DiagnosticCodeHighlight {
313
+ return {message, start, end: {line: end.line, column: end.column - 1}};
314
+ }
315
+
267
316
  /** Sanitizes object keys before using them as <code>key</code> in generateJSONCodeHighlights */
268
317
  export function encodeJSONKeyComponent(component: string): string {
269
- return component.replace(/\//g, '~1');
318
+ return component.replace(/~/g, '~0').replace(/\//g, '~1');
270
319
  }
271
320
 
272
321
  const escapeCharacters = ['\\', '*', '_', '~'];
@@ -280,8 +329,7 @@ export function escapeMarkdown(s: string): string {
280
329
  return result;
281
330
  }
282
331
 
283
- // $FlowFixMe[unclear-type]
284
- type TemplateInput = any;
332
+ type TemplateInput = $FlowFixMe;
285
333
 
286
334
  const mdVerbatim = Symbol();
287
335
  export function md(
@@ -290,31 +338,39 @@ export function md(
290
338
  ): string {
291
339
  let result = [];
292
340
  for (let i = 0; i < params.length; i++) {
341
+ result.push(strings[i]);
342
+
293
343
  let param = params[i];
294
- result.push(
295
- strings[i],
296
- param?.[mdVerbatim] ? param.value : escapeMarkdown(`${param}`),
297
- );
344
+ if (Array.isArray(param)) {
345
+ for (let j = 0; j < param.length; j++) {
346
+ result.push(param[j]?.[mdVerbatim] ?? escapeMarkdown(`${param[j]}`));
347
+ if (j < param.length - 1) {
348
+ result.push(', ');
349
+ }
350
+ }
351
+ } else {
352
+ result.push(param?.[mdVerbatim] ?? escapeMarkdown(`${param}`));
353
+ }
298
354
  }
299
355
  return result.join('') + strings[strings.length - 1];
300
356
  }
301
357
 
302
- md.bold = function(s: TemplateInput): {|value: string|} {
358
+ md.bold = function (s: TemplateInput): TemplateInput {
303
359
  // $FlowFixMe[invalid-computed-prop]
304
- return {[mdVerbatim]: true, value: '**' + escapeMarkdown(`${s}`) + '**'};
360
+ return {[mdVerbatim]: '**' + escapeMarkdown(`${s}`) + '**'};
305
361
  };
306
362
 
307
- md.italic = function(s: TemplateInput): {|value: string|} {
363
+ md.italic = function (s: TemplateInput): TemplateInput {
308
364
  // $FlowFixMe[invalid-computed-prop]
309
- return {[mdVerbatim]: true, value: '_' + escapeMarkdown(`${s}`) + '_'};
365
+ return {[mdVerbatim]: '_' + escapeMarkdown(`${s}`) + '_'};
310
366
  };
311
367
 
312
- md.underline = function(s: TemplateInput): {|value: string|} {
368
+ md.underline = function (s: TemplateInput): TemplateInput {
313
369
  // $FlowFixMe[invalid-computed-prop]
314
- return {[mdVerbatim]: true, value: '__' + escapeMarkdown(`${s}`) + '__'};
370
+ return {[mdVerbatim]: '__' + escapeMarkdown(`${s}`) + '__'};
315
371
  };
316
372
 
317
- md.strikethrough = function(s: TemplateInput): {|value: string|} {
373
+ md.strikethrough = function (s: TemplateInput): TemplateInput {
318
374
  // $FlowFixMe[invalid-computed-prop]
319
- return {[mdVerbatim]: true, value: '~~' + escapeMarkdown(`${s}`) + '~~'};
375
+ return {[mdVerbatim]: '~~' + escapeMarkdown(`${s}`) + '~~'};
320
376
  };
@@ -0,0 +1,36 @@
1
+ // @flow strict-local
2
+ import assert from 'assert';
3
+
4
+ import {generateJSONCodeHighlights} from '../src/diagnostic';
5
+
6
+ describe('generateJSONCodeHighlights', () => {
7
+ it('returns an escaped string 01', () => {
8
+ let result = generateJSONCodeHighlights(
9
+ `{
10
+ "a": 1
11
+ }`,
12
+ [
13
+ {key: '/a', type: 'key', message: 'foo1'},
14
+ {key: '/a', type: 'value', message: 'foo2'},
15
+ {key: '/a', message: 'foo3'},
16
+ ],
17
+ );
18
+ assert.deepEqual(result, [
19
+ {
20
+ start: {line: 2, column: 3},
21
+ end: {line: 2, column: 5},
22
+ message: 'foo1',
23
+ },
24
+ {
25
+ start: {line: 2, column: 8},
26
+ end: {line: 2, column: 8},
27
+ message: 'foo2',
28
+ },
29
+ {
30
+ start: {line: 2, column: 3},
31
+ end: {line: 2, column: 8},
32
+ message: 'foo3',
33
+ },
34
+ ]);
35
+ });
36
+ });