You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

174 lines
5.1 KiB
JavaScript

"use strict";
// tag
/* FIXME: How are validator callbacks handled? */
const mapObj = require("map-obj");
const objectHash = require("object-hash");
const util = require("util");
const filterTypeRules = require("./util/filter-type-rules");
function hashType(type, level = 0, seenTypes = new Map()) {
/* Type hashes should always be fully local; that is, they are calculated entirely from the point of view of the top-level type being hashed, with respect to how circular type references are handled. This means that we may effectively be calculating a hash for a type more than once, but it's necessary to ensure determinism; if we didn't do this and globally cached hashes for each type definition, then switching around the definition order of types could cause rippling hash changes throughout every referenced type, because circular type references are handled by recording the relative location of the first-seen instance of a type. */
function canonicalRule(rule, level) {
let props;
if (rule == null) {
return rule;
} else if (rule._baseType != null) {
let options;
if (rule._baseType === "guardedFunction") {
options = {
args: rule._options.args.map((arg) => {
return canonicalRule(arg, level + 1);
}),
returnType: canonicalRule(rule._options.returnType, level + 1)
};
} else {
options = rule._options;
}
props = {
ruleType: "baseType",
baseType: rule._baseType,
options: options
};
} else if (rule._collectionType != null) {
props = {
ruleType: "collection",
keyType: canonicalRule(rule._keyType, level + 1),
itemType: canonicalRule(rule._itemType, level + 1),
options: rule._options
};
} else if (rule._modifierType != null) {
if (rule._modifierType === "either") {
props = {
ruleType: "either",
types: rule._types.map((type) => {
return canonicalRule(type, level + 1);
})
};
} else {
throw new Error(`Encountered unexpected modifier: ${rule._modifierType}`);
}
} else if (rule._isSlotRule) {
props = {
ruleType: "slot"
};
} else if (rule._isSelfRule) {
props = {
ruleType: "self"
};
} else if (rule._constructorType != null) {
props = {
ruleType: "instance",
constructor: rule._constructorType
};
} else if (rule._isCustomType || rule._isCustomRegistryType) {
let actualType;
if (rule._isCustomRegistryType) {
actualType = rule._registry._getType(rule._name);
} else {
actualType = rule;
}
let seenLevel = seenTypes.get(actualType);
/* We ignore same-level seen types, because those are sibling properties on the same type. Duplication there is acceptable. */
if (seenLevel != null && seenLevel < level) {
props = {
ruleType: "typeReference",
levels: level - seenTypes.get(actualType)
};
} else {
seenTypes.set(actualType, level);
props = {
ruleType: "type",
typeHash: hashType(actualType, level + 1, seenTypes)
};
}
} else if (rule._isTrait || rule._isRegistryTrait) {
let actualTrait;
if (rule._isRegistryTrait) {
actualTrait = rule._registry._getTrait(rule._name);
} else {
actualTrait = rule;
}
let seenLevel = seenTypes.get(actualTrait);
if (seenLevel != null && seenLevel < level) {
props = {
ruleType: "traitReference",
levels: level - seenTypes.get(actualTrait)
};
} else {
seenTypes.set(actualTrait, level);
props = {
ruleType: "trait",
typeHash: hashType(actualTrait, level + 1, seenTypes)
};
}
} else if (rule._isTypeAlias === true) {
/* Should we wrap this in an `alias` to represent alias-level additional constraints? Or should we just return the canonical rule for the _alias itself? */
props = {
ruleType: "alias",
type: rule._alias
};
} else {
throw new Error(`Unrecognized rule type: ${util.inspect(rule)}`);
}
for (let [seenRule, seenLevel] of seenTypes) {
if (seenLevel > level) {
seenTypes.delete(seenRule);
}
}
return Object.assign(props, {
/* FIXME: Do processing to allow instances of types as default value? */
constraints: rule._constraints
});
// return Object.assign(props, {
// constraints: rule._constraints.map((constraint) => {
// return canonicalRule(constraint, level + 1);
// })
// });
}
seenTypes.set(type, level);
/* FIXME: Constraints for custom types / traits? */
if (type._isCustomType) {
return mapObj(filterTypeRules(type._cumulativeSchema), (key, value) => {
return [key, canonicalRule(value, level)];
});
} else if (type._isTrait) {
return mapObj(filterTypeRules(type._schema), (key, value) => {
return [key, canonicalRule(value, level)];
});
} else if (type._isTypeAlias) {
return {
_ruleType: "typeAlias",
type: canonicalRule(type._alias)
};
} else {
throw new Error("Can only hash types and traits");
}
}
module.exports = function generateTypeHash(type) {
return objectHash(hashType(type), {
algorithm: "sha1", /* FIXME: Switch to SHA256, after figuring out how to make this work in Webpack */
respectFunctionNames: false,
respectFunctionProperties: false
}).slice(0, 6);
};