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