Files
babel/packages/babel-parser/test/helpers/difference.js
Francisco Ryan Tolmasky IandGitHub 93c84126bb Fuzz test location-related parser options (#14201)
* Autogenerate tests to make sure startLine and startColumn work everywhere. Also improve the performance of tests so that the fact that we have ~88,000 tests now doesn't really cause the tests to run any slower.

* Only deserialize JSON files that are serialized.

* Don't fuzz test in node 8, since for whatever reason Jest 23 on node 8 takes forever after a certain number of tests, even if those tests are empty.

* Rename runFixtureTests.js to run-fixture-tests.js.

* Fix for showing code snippet again.

* Fix some linter errors.

* Fix stack traces and only generate the context error when we need it.

* Create errors in deserialization step instead of using adjust to convert them.

* Fix only storing cause if it's an error.

* Fix UnexpectedSuccess error.

* Better DifferentError and cleaned up the cause stuff a bit.

* Fix linter errors.

* First pass at serialization.

* Better errors and serialization.

* Fix saving output.

* Fix saving options.

* Fix linter errors and incorrect removal.

* Add FUZZ environment variable.

* Fix location undefined problem when saving.

* Read the actual options file in since we don't have a true copy of the original options.

* Fix also compacting start and end outside of loc.

* Fix linter error.

* Address a few style issues.

* Use environment variable for TEST_FUZZ, and disable fuzz testing for Node 6, 8, and 10.

* Address more change requests.

* Fix lint error.

* Move logic into FixtureError base class and add comments.

* Throw early if we are in CI, and make sure runFixtureText has its JSDocs comments.

* Fix JSDocs.

* Change fuzz testing to be opt-in while we only do line changes.

* Don't check if file exists before deleting it, and only overwite options if there's a throw property.

* Put a newline at the end of JSON files.

* Only refrain from throwing if the error is ENOENT. Also, clean up the error message when there is no cause.

* Fix linter error.
2022-01-29 08:48:12 +01:00

150 lines
3.8 KiB
JavaScript

/* eslint-disable no-confusing-arrow */
import { isIdentifierName } from "@babel/helper-validator-identifier";
const { isArray } = Array;
const { isInteger } = Number;
const { hasOwnProperty } = Object;
export default class Difference {
constructor(adjust, expected, actual) {
const woundDifference = compare(adjust, expected, actual);
if (!woundDifference) {
return Difference.None;
}
const [path, reason] = toUnwoundDifference(woundDifference);
const message = `${toExplanationString(reason)} in ${toPathString(path)}`;
return Object.assign(this, { ...reason, path, message });
}
}
Difference.None = Object.freeze(
Object.setPrototypeOf({}, Difference.prototype),
);
const toType = value =>
value === null
? "null"
: typeof value !== "object"
? typeof value
: isArray(value)
? "Array"
: value instanceof RegExp
? "RegExp"
: value instanceof Error
? "Error"
: "Object";
function compare(adjust, expected, actual) {
// easy.
if (Object.is(expected, actual)) {
return false;
}
const typeExpected = toType(expected);
const typeActual = toType(actual);
if (typeExpected !== typeActual) {
return { discrepancy: "value", expected, actual };
}
// Just ignore functions (AKA, assume they're equal).
if (typeActual === "function") {
return false;
}
if (typeActual === "RegExp" && expected + "" === actual + "") {
return false;
}
if (typeActual === "Error") {
return compare(
adjust,
{ message: expected.message },
{ message: actual.message },
);
}
if (typeActual !== "Object" && typeActual !== "Array") {
return { discrepancy: "value", expected, actual };
}
const keysExpected = Object.keys(expected);
const keysActual = Object.keys(actual).filter(
key => actual[key] !== void 0 && typeof actual[key] !== "function",
);
const lengthExpected = keysExpected.length;
const lengthActual = keysActual.length;
if (lengthExpected !== lengthActual && typeActual === "Array") {
return {
discrepancy: "length",
expected: lengthExpected,
actual: lengthActual,
};
}
if (lengthExpected < lengthActual) {
const keysExpectedSet = new Set(keysExpected);
const key = keysActual.find(key => !keysExpectedSet.has(key));
if (key !== void 0) {
return { discrepancy: "unexpected-key", key };
}
}
for (const key of keysExpected) {
if (!hasOwnProperty.call(actual, key)) {
return { discrepancy: "missing-key", key, actual };
}
const original = expected[key];
const adjusted = adjust
? adjust(adjust, original, key, expected)
: original;
const difference = compare(adjust, adjusted, actual[key]);
if (difference) {
return [key, difference];
}
}
return false;
}
const toUnwoundDifference = compiled =>
!isArray(compiled)
? [[], compiled]
: toUnwoundDifference(compiled[1]).map((item, index) =>
index === 0 ? [compiled[0], ...item] : item,
);
const toValueString = (value, type = toType(value)) =>
type === "string"
? JSON.stringify(value)
: type === "symbol"
? value.toString()
: type === "bigint"
? `${value}n`
: Object.is(value, -0)
? "-0"
: value + "";
const toExplanationString = ({ discrepancy, expected, actual, key }) =>
discrepancy === "length"
? `Array of wrong size, expected length of ${expected}, but got ${actual}`
: discrepancy === "unexpected-key"
? `Did not expect a property ${toValueString(key)}`
: discrepancy === "missing-key"
? `${toType(actual)} is missing property ${toValueString(key)}`
: `${toValueString(expected)} != ${toValueString(actual)}`;
const isInt = key => isInteger(+key);
const toAccess = key =>
isInt(key) ? `[${key}]` : isIdentifierName(key) ? `.${key}` : `["${key}"]`;
const toPathString = path => path.map(toAccess).join("");