Commit 34b74747 authored by Stefan Cameron's avatar Stefan Cameron
Browse files

Fix fullyQualify() bug with Array typesets containing qualified shapes

See CHANGELOG.md entry in this commit for more info.
parent 3b48701f
Pipeline #249123710 passed with stages
in 3 minutes and 10 seconds
......@@ -993,7 +993,7 @@ Checks a value using a single type.
| Param | Type | Description |
| --- | --- | --- |
| value | <code>\*</code> | Value to check. |
| singleType | <code>string</code> \| <code>Array</code> \| <code>Object</code> | Either a simple type name (one of [types](#rtvref.types.types)), a [shape descriptor](#rtvref.types.shape_descriptor), or an Array [typeset](#rtvref.types.typeset) which represents a single type. In the string/simple case, the [default qualifier](#rtvref.qualifiers.DEFAULT_QUALIFIER) is assumed. In the shape descriptor case, the [default object type](#rtvref.types.DEFAULT_OBJECT_TYPE) is assumed. In the Array case, the qualifier is optional, and a type, along with args, if any, is expected (e.g. `[type]`, `[qualifier, type]`, `[type, args]`, or `[qualifier, type, args]`). Note that the type may be implied the shorthand notation is being used for an ARRAY, or if the [default object type](#rtvref.types.DEFAULT_OBJECT_TYPE) is being implied. NOTE: A [custom validator](#rtvref.types.custom_validator) is not considered a valid single type. It's also considered a __separate type__ if it were passed-in via an Array, e.g. `[STRING, validator]`, which would violate the fact that `singleType` should be one type, and therefore cause an exception to be thrown. |
| singleType | <code>string</code> \| <code>Array</code> \| <code>Object</code> | Either a simple type name (one of [types](#rtvref.types.types)), a [shape descriptor](#rtvref.types.shape_descriptor), or an Array [typeset](#rtvref.types.typeset) which represents a single type. In the string/simple case, the [default qualifier](#rtvref.qualifiers.DEFAULT_QUALIFIER) is assumed. In the shape descriptor case, the [default object type](#rtvref.types.DEFAULT_OBJECT_TYPE) is assumed. In the Array case, the qualifier is optional, and a type, along with args, if any, is expected (e.g. `[type]`, `[qualifier, type]`, `[type, args]`, or `[qualifier, type, args]`). Note that the type may be implied if the shorthand notation is being used for an ARRAY, or if the [default object type](#rtvref.types.DEFAULT_OBJECT_TYPE) is being implied for a shape, e.g. `[{foo: rtv.STRING}]`. NOTE: A [custom validator](#rtvref.types.custom_validator) is not considered a valid single type. It's also considered a __separate type__ if it were passed-in via an Array, e.g. `[STRING, validator]`, which would violate the fact that `singleType` should be one type, and therefore cause an exception to be thrown. |
| [context] | [<code>type\_validator\_context</code>](#rtvref.validator.type_validator_context) \| <code>undefined</code> | Additional context for the check. If _falsy_, a new context will be created for all downstream checks using `value` as the original value, and `undefined` as the parent. |
<a name="rtvref.impl.checkWithShape"></a>
......
......@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Date format is YYYY-MM-DD.
## UNRELEASED
Release date: TBD
### Changed
- Fixed: The `rtv.fullyQualify()` method was not properly supporting Array typesets with a qualifier and a shape, e.g. `[rtv.OPTIONAL, {foo: rtv.STRING}]`. The method now correctly returns `[rtv.OPTIONAL, rtv.OBJECT, {$ {foo: rtv.STRING}}]`.
## 3.1.2
Release date: 2021-01-14
......
......@@ -26,7 +26,7 @@ The following npm commands will get you started:
* Use Mocha's `describe.only()` and `it.only()` helpers to isolate the test you want to debug, but be sure to remove them when you're done, otherwise coverage will fail.
* `test:coverage`: Checks for code coverage after running unit tests, outputs to `/coverage`
* `lint`: Checks the code for lint.
* `start`: Builds the library, starts Node.js in debug mode (with `--inspect`), and automatically loads the library into the global environment as `rtv` (along with `lodash` as `ld`, and `Object.prototype.toString` as `ostr`) by running the following script: `/tools/node.js`
* `start`: Builds the library, starts Node.js in debug mode (with `--inspect`), and automatically loads the library into the global environment as `rtv` (along with `lodash` as `ld`, and `Object.prototype.toString` as `ostr`) by running the following script: `/tools/node.js`. Use DevTools or your favorite IDE to attach to the Node process to debug!
* `html`: Builds the library and starts `live-server` at http://localhost:8080 with a root of `/tools` where you can load any of the HTML files in that directory to manually test the build in a browser: [UMD](http://localhost:8080/rtvjs-umd.html) or [ESM](http://localhost:8080/rtvjs-esm.html).
See `/package.json` scripts for other commands.
......
......@@ -233,6 +233,7 @@ export const fullyQualify = function (typeset, qualifier) {
}
const fqts = []; // ALWAYS a new array
let hasQualifier = false; // true if a qualifier was found in first position
let curType; // @type {(string|undefined)} current type in scope or undefined if none
// typeset is an array: iterate its elements and build fqts iteratively
......@@ -240,6 +241,7 @@ export const fullyQualify = function (typeset, qualifier) {
// qualifiers are non-empty strings and must appear in the first element, if specified
if (i === 0) {
if (isString(rule) && qualifiers.check(rule)) {
hasQualifier = true;
fqts.push(qualifier || rule); // qualifier overrides the one in the typeset
return; // next rule
}
......@@ -252,8 +254,10 @@ export const fullyQualify = function (typeset, qualifier) {
// must be a type
curType = rule;
fqts.push(curType);
} else if (i === 0 && isShape(rule)) {
// nested shape descriptor using default object type: move shape into args
} else if ((i === 0 || (i === 1 && hasQualifier)) && isShape(rule)) {
// nested shape descriptor using default object type that is either first
// in the array (also using default qualifier), or second (because a
// qualifier is in the first position): move shape into args
curType = DEFAULT_OBJECT_TYPE;
fqts.push(curType, { $: rule });
} else if (isTypeArgs(rule)) {
......@@ -621,9 +625,10 @@ export const _getCheckOptions = function (current = {}) {
*
* In the Array case, the qualifier is optional, and a type, along with args,
* if any, is expected (e.g. `[type]`, `[qualifier, type]`, `[type, args]`, or
* `[qualifier, type, args]`). Note that the type may be implied the shorthand
* `[qualifier, type, args]`). Note that the type may be implied if the shorthand
* notation is being used for an ARRAY, or if the
* {@link rtvref.types.DEFAULT_OBJECT_TYPE default object type} is being implied.
* {@link rtvref.types.DEFAULT_OBJECT_TYPE default object type} is being implied
* for a shape, e.g. `[{foo: rtv.STRING}]`.
*
* NOTE: A {@link rtvref.types.custom_validator custom validator} is not considered
* a valid single type. It's also considered a __separate type__ if it were passed-in
......
......@@ -383,6 +383,26 @@ describe('module: lib/impl', function () {
]); // not deep
const shape = {};
ts = [shape];
fqts = impl.fullyQualify(ts);
expect(ts).not.to.equal(fqts); // should be a new array
expect(fqts).to.eql([
DEFAULT_QUALIFIER,
DEFAULT_OBJECT_TYPE,
{ $: shape },
]); // object is treated as shape, not array params
expect(fqts[2].$).to.equal(shape); // same object, not cloned
ts = [qualifiers.EXPECTED, shape];
fqts = impl.fullyQualify(ts);
expect(ts).not.to.equal(fqts); // should be a new array
expect(fqts).to.eql([
qualifiers.EXPECTED,
DEFAULT_OBJECT_TYPE,
{ $: shape },
]); // object is treated as shape, not array params
expect(fqts[2].$).to.equal(shape); // same object, not cloned
ts = [shape, [types.STRING]];
fqts = impl.fullyQualify(ts);
expect(ts).not.to.equal(fqts); // should be a new array
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment