Skip to content

Commit

Permalink
JSDoc: Logging (parcel-bundler#5085)
Browse files Browse the repository at this point in the history
  • Loading branch information
mischnic authored Aug 30, 2020
1 parent c4e494f commit b6ba17d
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 34 deletions.
109 changes: 78 additions & 31 deletions packages/core/diagnostic/src/diagnostic.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,76 @@
// @flow
// @flow strict-local
import type {FilePath} from '@parcel/types';

import invariant from 'assert';
// flowlint-next-line untyped-import:off
import jsonMap from 'json-source-map';
import nullthrows from 'nullthrows';

/** These positions are 1-based (so <code>1</code> is the first line/column) */
export type DiagnosticHighlightLocation = {|
// These positions are 1-based
+line: number,
+column: number,
|};

export type DiagnosticSeverity = 'error' | 'warn' | 'info';

// Note: A tab character is always counted as a single character
// This is to prevent any mismatch of highlighting across machines
/**
* Note: A tab character is always counted as a single character
* This is to prevent any mismatch of highlighting across machines
*/
export type DiagnosticCodeHighlight = {|
// start and end are included in the highlighted region
/** Location of the first character that should get highlighted for this highlight. */
start: DiagnosticHighlightLocation,
/** Location of the last character that should get highlighted for this highlight. */
end: DiagnosticHighlightLocation,
/** A message that should be displayed at this location in the code (optional). */
message?: string,
|};

/**
* Describes how to format a code frame.
* A code frame is a visualization of a piece of code with a certain amount of
* code highlights that point to certain chunk(s) inside the code.
*/
export type DiagnosticCodeFrame = {|
// if no code is passed, it will be read in from Diagnostic#filePath
/**
* The contents of the source file.
*
* If no code is passed, it will be read in from Diagnostic#filePath, remember that
* the asset's current code could be different from the input contents.
*
*/
code?: string,
codeHighlights: DiagnosticCodeHighlight | Array<DiagnosticCodeHighlight>,
|};

// A Diagnostic is a style agnostic way of emitting errors, warnings and info
// The reporter's are responsible for rendering the message, codeframes, hints, ...
/**
* A style agnostic way of emitting errors, warnings and info.
* Reporters are responsible for rendering the message, codeframes, hints, ...
*/
export type Diagnostic = {|
/** This is the message you want to log. */
message: string,
origin?: string, // Name of plugin or file that threw this error
/** Name of plugin or file that threw this error */
origin?: string,

// basic error data
/** A stacktrace of the error (optional) */
stack?: string,
/** Name of the error (optional) */
name?: string,

// Asset metadata, filePath is absolute or relative to the project root
/** Path to the file this diagnostic is about (optional, absolute or relative to the project root) */
filePath?: FilePath,
/** Language of the file this diagnostic is about (optional) */
language?: string,

// Codeframe data
/** A code frame points to a certain location(s) in the file this diagnostic is linked to (optional) */
codeFrame?: DiagnosticCodeFrame,

// Hints to resolve issues faster
/** An optional list of strings that suggest ways to resolve this issue */
hints?: Array<string>,

/** @private */
skipFormatting?: boolean,
|};

Expand All @@ -70,14 +94,15 @@ export type DiagnosticWithoutOrigin = {|
origin?: string,
|};

// Something that can be turned into a diagnostic...
/** Something that can be turned into a diagnostic. */
export type Diagnostifiable =
| Diagnostic
| Array<Diagnostic>
| ThrowableDiagnostic
| PrintableError
| string;

/** Normalize the given value into a diagnostic. */
export function anyToDiagnostic(
input: Diagnostifiable,
): Diagnostic | Array<Diagnostic> {
Expand All @@ -92,6 +117,7 @@ export function anyToDiagnostic(
return diagnostic;
}

/** Normalize the given error into a diagnostic. */
export function errorToDiagnostic(
error: ThrowableDiagnostic | PrintableError | string,
realOrigin?: string,
Expand All @@ -100,7 +126,7 @@ export function errorToDiagnostic(

if (typeof error === 'string') {
return {
origin: realOrigin || 'Error',
origin: realOrigin ?? 'Error',
message: error,
codeFrame,
};
Expand All @@ -110,12 +136,12 @@ export function errorToDiagnostic(
return error.diagnostics.map(d => {
return {
...d,
origin: realOrigin || d.origin || 'unknown',
origin: realOrigin ?? d.origin ?? 'unknown',
};
});
}

if (error.loc && error.source) {
if (error.loc && error.source != null) {
codeFrame = {
code: error.source,
codeHighlights: {
Expand All @@ -132,38 +158,44 @@ export function errorToDiagnostic(
}

return {
origin: realOrigin || 'Error',
origin: realOrigin ?? 'Error',
message: error.message,
name: error.name,
filePath: error.filePath || error.fileName,
stack: error.highlightedCodeFrame || error.codeFrame || error.stack,
filePath: error.filePath ?? error.fileName,
stack: error.highlightedCodeFrame ?? error.codeFrame ?? error.stack,
codeFrame,
};
}

type ThrowableDiagnosticOpts = {
diagnostic: Diagnostic | Array<Diagnostic>,
...
};

/**
* An error wrapper around a diagnostic that can be <code>throw</code>n (e.g. to signal a
* build error).
*/
export default class ThrowableDiagnostic extends Error {
diagnostics: Array<Diagnostic>;

constructor(opts: ThrowableDiagnosticOpts) {
constructor(opts: {diagnostic: Diagnostic | Array<Diagnostic>, ...}) {
let diagnostics = Array.isArray(opts.diagnostic)
? opts.diagnostic
: [opts.diagnostic];

// construct error from diagnostics...
super(diagnostics[0].message);
this.stack = diagnostics[0].stack || super.stack;
this.name = diagnostics[0].name || super.name;
this.stack = diagnostics[0].stack ?? super.stack;
this.name = diagnostics[0].name ?? super.name;

this.diagnostics = diagnostics;
}
}

// ids.key has to be "/some/parent/child"
/**
* Turns a list of positions in a JSON file with messages into a list of diagnostics.
* Uses <a href="https://github.com/epoberezkin/json-source-map">epoberezkin/json-source-map</a>.
*
* @param code the JSON code
* @param ids A list of JSON keypaths (<code>key: "/some/parent/child"</code>) with corresponding messages, \
* <code>type</code> signifies whether the key of the value in a JSON object should be highlighted.
*/
export function generateJSONCodeHighlights(
code: string,
ids: Array<{|key: string, type?: ?'key' | 'value', message?: string|}>,
Expand All @@ -179,20 +211,34 @@ export function generateJSONCodeHighlights(
});
}

/**
* Converts entries in <a href="https://github.com/epoberezkin/json-source-map">epoberezkin/json-source-map</a>'s
* <code>result.pointers</code> array.
*/
export function getJSONSourceLocation(
pos: any,
pos: {|
value: {|line: number, column: number|},
valueEnd: {|line: number, column: number|},
...
| {||}
| {|
key: {|line: number, column: number|},
keyEnd: {|line: number, column: number|},
|},
|},
type?: ?'key' | 'value',
): {|
start: DiagnosticHighlightLocation,
end: DiagnosticHighlightLocation,
|} {
if (!type && pos.value) {
if (!type && pos.key && pos.value) {
// key and value
return {
start: {line: pos.key.line + 1, column: pos.key.column + 1},
end: {line: pos.valueEnd.line + 1, column: pos.valueEnd.column},
};
} else if (type == 'key' || !pos.value) {
invariant(pos.key);
return {
start: {line: pos.key.line + 1, column: pos.key.column + 1},
end: {line: pos.keyEnd.line + 1, column: pos.keyEnd.column},
Expand All @@ -205,6 +251,7 @@ export function getJSONSourceLocation(
}
}

/** Sanitizes object keys before using them as <code>key</code> in generateJSONCodeHighlights */
export function encodeJSONKeyComponent(component: string): string {
return component.replace(/\//g, '~1');
}
16 changes: 13 additions & 3 deletions packages/core/logger/src/Logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,21 @@ class Logger {
const logger: Logger = new Logger();
export default logger;

/** @private */
export type PluginLoggerOpts = {|
origin: string,
|};

export class PluginLogger {
/** @private */
origin: string;

/** @private */
constructor(opts: PluginLoggerOpts) {
this.origin = opts.origin;
}

/** @private */
updateOrigin(
diagnostic: DiagnosticWithoutOrigin | Array<DiagnosticWithoutOrigin>,
): Diagnostic | Array<Diagnostic> {
Expand Down Expand Up @@ -132,16 +136,21 @@ export class PluginLogger {
logger.error(input, this.origin);
}

/** @private */
progress(message: string): void {
logger.progress(message);
}
}

let consolePatched = false;
/** @private */
export const INTERNAL_ORIGINAL_CONSOLE = {...console};
let consolePatched = false;

// Patch `console` APIs within workers to forward their messages to the Logger
// at the appropriate levels.
/**
* Patch `console` APIs within workers to forward their messages to the Logger
* at the appropriate levels.
* @private
*/
export function patchConsole() {
// Skip if console is already patched...
if (consolePatched) return;
Expand Down Expand Up @@ -172,6 +181,7 @@ export function patchConsole() {
consolePatched = true;
}

/** @private */
export function unpatchConsole() {
// Skip if console isn't patched...
if (!consolePatched) return;
Expand Down

0 comments on commit b6ba17d

Please sign in to comment.