Skip to content
This repository has been archived by the owner on Aug 22, 2023. It is now read-only.

Improve pretty errors #42

Merged
merged 1 commit into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions src/errors/pretty-print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,40 +23,49 @@ export interface PrettyPrintableError {
/**
* a suggestion that may be useful or provide additional context
*/
suggestion?: string;
suggestions?: string[];
}

// These exist for backwards compatibility with CLIError
type CLIErrorDisplayOptions = { name?: string; bang?: string }

export function applyPrettyPrintOptions(error: Error, options: PrettyPrintableError): PrettyPrintableError {
const prettyErrorKeys: (keyof PrettyPrintableError)[] = ['message', 'code', 'ref', 'suggestion']
const prettyErrorKeys: (keyof PrettyPrintableError)[] = ['message', 'code', 'ref', 'suggestions']

prettyErrorKeys.forEach(key => {
const applyOptionsKey = !(key in error) && options[key]
if (applyOptionsKey) {
(error as PrettyPrintableError)[key] = options[key]
(error as any)[key] = options[key]
}
})

return error
}

const formatSuggestions = (suggestions?: string[]): string | undefined => {
const label = 'Try this:'
if (!suggestions || suggestions.length === 0) return undefined
if (suggestions.length === 1) return `${label} ${suggestions[0]}`

const multiple = suggestions.map(suggestion => `* ${suggestion}`).join('\n')
return `${label}\n${indent(multiple, 2)}`
}

export default function prettyPrint(error: Error & PrettyPrintableError & CLIErrorDisplayOptions) {
if (config.debug) {
return error.stack
}

const {message, code, suggestion, ref, name: errorSuffix, bang} = error
const {message, code, suggestions, ref, name: errorSuffix, bang} = error

// errorSuffix is pulled from the 'name' property on CLIError
// and is like either Error or Warning
const formattedHeader = message ? `${errorSuffix || 'Error'}: ${message}` : undefined
const formattedCode = code ? `Code: ${code}` : undefined
const formattedSuggestion = suggestion ? `Suggestion: ${suggestion}` : undefined
const formattedSuggestions = formatSuggestions(suggestions)
const formattedReference = ref ? `Reference: ${ref}` : undefined

const formatted = [formattedHeader, formattedCode, formattedSuggestion, formattedReference]
const formatted = [formattedHeader, formattedCode, formattedSuggestions, formattedReference]
.filter(Boolean)
.join('\n')

Expand Down
14 changes: 7 additions & 7 deletions test/error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@ describe('error', () => {

fancy
.do(() => {
error('An error happened!', {code: 'ERR', ref: 'https://oclif.com/error', suggestion: 'rm -rf node_modules'})
error('An error happened!', {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']})
})
.catch((error: PrettyPrintableError) => {
expect(error.message).to.equal('An error happened!')
expect(error.code).to.equal('ERR')
expect(error.ref).to.equal('https://oclif.com/error')
expect(error.suggestion).to.equal('rm -rf node_modules')
expect(error.suggestions).to.deep.equal(['rm -rf node_modules'])
})
.it('attaches pretty print properties to a new error from options')

fancy
.do(() => {
error(new Error('An existing error object error!'), {code: 'ERR', ref: 'https://oclif.com/error', suggestion: 'rm -rf node_modules'})
error(new Error('An existing error object error!'), {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']})
})
.catch((error: PrettyPrintableError) => {
expect(error.message).to.equal('An existing error object error!')
expect(error.code).to.equal('ERR')
expect(error.ref).to.equal('https://oclif.com/error')
expect(error.suggestion).to.equal('rm -rf node_modules')
expect(error.suggestions).to.deep.equal(['rm -rf node_modules'])
})
.it('attached pretty print properties from options to an existing error object')

Expand All @@ -41,13 +41,13 @@ describe('error', () => {
const e: any = new Error('An existing error object error!')
e.code = 'ORIG_ERR'
e.ref = 'ORIG_REF'
e.suggestion = 'ORIG_SUGGESTION'
error(e, {code: 'ERR', ref: 'https://oclif.com/error', suggestion: 'rm -rf node_modules'})
e.suggestions = ['ORIG_SUGGESTION']
error(e, {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']})
})
.catch((error: PrettyPrintableError) => {
expect(error.code).to.equal('ORIG_ERR')
expect(error.ref).to.equal('ORIG_REF')
expect(error.suggestion).to.equal('ORIG_SUGGESTION')
expect(error.suggestions).to.deep.equal(['ORIG_SUGGESTION'])
})
.it('preserves original pretty printable properties and is not overwritten by options')

Expand Down
16 changes: 14 additions & 2 deletions test/pretty-print.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,28 @@ describe('pretty-print', () => {
const sampleError: Error & PrettyPrintableError = new Error('Something very serious has gone wrong with the flags!')
sampleError.ref = 'https://oclif.io/docs/flags'
sampleError.code = 'OCLIF_BAD_FLAG'
sampleError.suggestion = 'Try using using a good flag'
sampleError.suggestions = ['Try using using a good flag']

expect(
stripAnsi(prettyPrint(sampleError)),
).to.equal(` Error: Something very serious has gone wrong with the flags!
Code: OCLIF_BAD_FLAG
Suggestion: Try using using a good flag
Try this: Try using using a good flag
Reference: https://oclif.io/docs/flags`)
})

fancy
.it('pretty prints multiple suggestions', async () => {
const sampleError: Error & PrettyPrintableError = new Error('Something very serious has gone wrong with the flags!')
sampleError.suggestions = ['Use a good flag', 'Use no flags']
expect(
stripAnsi(prettyPrint(sampleError)),
).to.equal(` Error: Something very serious has gone wrong with the flags!
Try this:
* Use a good flag
* Use no flags`)
})

fancy
.it('pretty prints with omitted fields', async () => {
const sampleError = new Error('Something very serious has gone wrong with the flags!')
Expand Down