@validatem/core
Advanced tools
Comparing version 0.3.7 to 0.3.8
@@ -7,2 +7,3 @@ "use strict"; | ||
module.exports = { | ||
// TODO: Provide a 'basePathsToIgnore' option for all of the below methods, so that third-party modules could wrap these methods without having themselves show up as the source of the error? Their base paths can then be treated like internals paths in the error parsing code that pinpoints the validation call site. | ||
validateArguments: require("./src/api/validate-arguments"), | ||
@@ -12,3 +13,4 @@ validateOptions: require("./src/api/validate-options"), | ||
AggregrateValidationError: require("./src/aggregrate-validation-error") | ||
AggregrateValidationError: require("./src/aggregrate-validation-error"), | ||
RemainingArguments: require("./src/api/validate-arguments/remaining-arguments-symbol") | ||
}; |
@@ -10,3 +10,3 @@ { | ||
], | ||
"version": "0.3.7", | ||
"version": "0.3.8", | ||
"main": "index.js", | ||
@@ -27,2 +27,3 @@ "repository": "http://git.cryto.net/validatem/core.git", | ||
"@validatem/validation-result": "^0.1.1", | ||
"@validatem/virtual-property": "^0.1.0", | ||
"as-expression": "^1.0.0", | ||
@@ -32,6 +33,8 @@ "assure-array": "^1.0.0", | ||
"default-value": "^1.0.0", | ||
"execall": "^2.0.0", | ||
"flatten": "^1.0.3", | ||
"indent-string": "^4.0.0", | ||
"is-arguments": "^1.0.4", | ||
"supports-color": "^7.1.0" | ||
"supports-color": "^7.1.0", | ||
"syncpipe": "^1.0.0" | ||
}, | ||
@@ -38,0 +41,0 @@ "devDependencies": { |
"use strict"; | ||
const indentString = require("indent-string"); | ||
const supportsColor = require("supports-color"); | ||
const matchVirtualProperty = require("@validatem/match-virtual-property"); | ||
const asExpression = require("as-expression"); | ||
const syncpipe = require("syncpipe"); | ||
const AggregrateValidationError = require("./aggregrate-validation-error"); | ||
const parseStacktrace = require("./parse-stacktrace"); | ||
const { dim, dimBold, highlight, highlightBold } = require("./colors"); | ||
@@ -19,16 +21,2 @@ // TODO: Omit the "At (root)" for path-less errors, to avoid confusion when singular values are being compared? | ||
// NOTE: We do some manual ANSI escape code stuff here for now, because using `chalk` would significantly inflate the bundle size of the core. | ||
// TODO: Find a better solution for this. | ||
let openHighlight, openDim, closeColor; | ||
if (supportsColor.stderr) { | ||
openHighlight = `\u001b[36m`; // cyan | ||
openDim = `\u001b[90m`; // gray | ||
closeColor = `\u001b[39m`; | ||
} else { | ||
openHighlight = ""; | ||
openDim = ""; | ||
closeColor = ""; | ||
} | ||
function renderErrorList(errors, subErrorLevels = 0) { | ||
@@ -40,5 +28,5 @@ let rephrasedErrors = errors.map((error, i) => { | ||
} else if (typeof segment === "string" || typeof segment === "number") { | ||
return openHighlight + String(segment) + closeColor; | ||
return highlight(String(segment)); | ||
} else if (matchVirtualProperty(segment)) { | ||
return openDim + `(${segment.name})` + closeColor; | ||
return dim(`(${segment.name})`); | ||
} else { | ||
@@ -59,3 +47,3 @@ throw new Error(`Unexpected path segment encountered: ${segment}; this is a bug, please report it!`); | ||
return indentString(message, subErrorLevels * 4); | ||
return message; | ||
} else { | ||
@@ -70,4 +58,13 @@ return (pathSegments.length > 0) | ||
let renderedSubErrors = renderErrorList(error.subErrors, subErrorLevels + 1); | ||
let isLastError = (i === errors.length - 1); | ||
return mainLine + "\n" + renderedSubErrors; | ||
if (subErrorLevels > 0 && !isLastError) { | ||
return syncpipe(renderedSubErrors, [ | ||
(_) => indentString(_, 3), | ||
(_) => indentString(_, 1, { indent: "│" }), | ||
(_) => mainLine + "\n" + _ | ||
]); | ||
} else { | ||
return mainLine + "\n" + indentString(renderedSubErrors, 4); | ||
} | ||
} else { | ||
@@ -83,7 +80,80 @@ return mainLine; | ||
function determineLocation() { | ||
try { | ||
throw new Error(`Dummy error to obtain a stacktrace`); | ||
} catch (error) { | ||
try { | ||
return syncpipe(error, [ | ||
(_) => parseStacktrace(_), | ||
(_) => removeInternalFrames(_), | ||
(_) => _[0], | ||
(_) => { | ||
return { | ||
... _, | ||
shortPath: abbreviatePath(_.location.path) | ||
}; | ||
} | ||
]); | ||
} catch (parsingError) { | ||
// If *anything at all* went wrong, we will just give up and return nothing, because the stacktrace parsing code is fragile, and we don't want that to be a reason for someone not to get a validation error displayed to them. | ||
// FIXME: Do we want to have this visible as a warning in 1.0.0? Or should this warning be opt-in, for when the user wants more detail about *why* the location of the error could not be determined? Since there may be legitimate reasons for that to occur, eg. in bundled code without source maps. | ||
console.warn("An error occurred during stacktrace parsing; please report this as a bug in @validatem/core!", parsingError); | ||
} | ||
} | ||
} | ||
// NOTE: This must be changed if aggregrate-errors.js is ever moved within the module! | ||
let internalBasePathRegex = /(.+)src$/; | ||
function getInternalBasePath() { | ||
let match = internalBasePathRegex.exec(__dirname); | ||
if (match != null) { | ||
return match[1]; | ||
} else { | ||
throw new Error(`Did not find expected basePath, instead got: ${__dirname}`); | ||
} | ||
} | ||
function removeInternalFrames(stack) { | ||
let internalBasePath = getInternalBasePath(); | ||
return stack.filter((frame) => { | ||
return (!frame.location.path.startsWith(internalBasePath)); | ||
}); | ||
} | ||
function abbreviatePath(path) { | ||
// TODO: Maybe add a special case for paths within node_modules? For when an error originates from a package the user is depending on. | ||
let segments = path.split(/[\\\/]/); | ||
let [ thirdLast, secondLast, last ] = segments.slice(-3); | ||
if (last != null) { | ||
let isIndexFile = /^index\.[a-z0-9]+$/.test(last); | ||
let relevantSegments = (isIndexFile) | ||
? [ thirdLast, secondLast, last ] | ||
: [ secondLast, last ]; | ||
return relevantSegments.join("/"); | ||
} else { | ||
// This path is extremely short, so we'll just return it as-is. | ||
return segments.join("/"); | ||
} | ||
} | ||
module.exports = function aggregrateAndThrowErrors(errors) { | ||
let detailLines = renderErrorList(errors); | ||
if (errors.length > 0) { | ||
let detailLines = renderErrorList(errors); | ||
let frame = determineLocation(); | ||
if (errors.length > 0) { | ||
return new AggregrateValidationError(`One or more validation errors occurred:\n${detailLines}`, { | ||
let functionString = (frame.alias != null) | ||
? `${frame.alias} [${frame.functionName}]` | ||
: frame.functionName; | ||
let locationString = (frame != null) | ||
? `${highlightBold(functionString)} in ${highlightBold(frame.shortPath)}, at line ${highlightBold(frame.location.line)} (${frame.location.path})` | ||
: dimBold(`(could not determine location)`); | ||
return new AggregrateValidationError(`One or more validation errors occurred at ${locationString}:\n${detailLines}`, { | ||
errors: errors | ||
@@ -90,0 +160,0 @@ }); |
32573
21
583
21
+ Addedexecall@^2.0.0
+ Addedsyncpipe@^1.0.0
+ Addedclone-regexp@2.2.0(transitive)
+ Addedexecall@2.0.0(transitive)
+ Addedis-regexp@2.1.0(transitive)
+ Addedsyncpipe@1.0.0(transitive)