UNPKG

@atlaskit/adf-utils

Version:

Set of utilities to traverse, modify and create ADF documents.

1,040 lines (1,023 loc) 44.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof3 = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.createSpec = createSpec; exports.validateAttrs = validateAttrs; exports.validator = validator; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure"); var specs = _interopRequireWildcard(require("./specs")); var _utils = require("./utils"); var _extractAllowedContent = require("./extractAllowedContent"); var _rules = require("./rules"); var _excluded = ["items"]; // Ignored via go/ees005 // eslint-disable-next-line import/no-namespace function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof3(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any function mapMarksItems(spec) { var fn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (x) { return x; }; if (spec.props && spec.props.marks) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var _ref = spec.props.marks, items = _ref.items, rest = (0, _objectWithoutProperties2.default)(_ref, _excluded); return _objectSpread(_objectSpread({}, spec), {}, { props: _objectSpread(_objectSpread({}, spec.props), {}, { marks: _objectSpread(_objectSpread({}, rest), {}, { /** * `Text & MarksObject<Mark-1>` produces `items: ['mark-1']` * `Text & MarksObject<Mark-1 | Mark-2>` produces `items: [['mark-1', 'mark-2']]` */ items: items.length ? Array.isArray(items[0]) ? items.map(fn) : [fn(items)] : [[]] }) }) }); } else { return spec; } } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var partitionObject = function partitionObject(obj, predicate) { return Object.keys(obj).reduce(function (acc, key) { var result = predicate(key, obj[key], obj); acc[result ? 0 : 1].push(key); return acc; }, [[], []]); }; /** * Checks if a spec is a variant spec. * A variant spec is an array where the first element is a string (base spec name) * and the second element is a ValidatorSpec object { props: { ... } } * * @param spec - The spec to check * @returns true if the spec is a variant spec, false otherwise */ var isVariant = function isVariant(spec) { return (0, _typeof2.default)(spec) === 'object' && !!spec && 0 in spec && 1 in spec && typeof spec[0] === 'string' && (0, _typeof2.default)(spec[1]) === 'object'; }; /** * Normalizes the structure of files imported from './specs'. * We denormalised the spec to save bundle size. */ function createSpec(nodes, marks) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any return Object.keys(specs).reduce(function (newSpecs, k) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var spec = _objectSpread({}, specs[k]); if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_schema', 'isEnabled', true) && isVariant(spec) && // Only apply to variants which are explicitly marked for override in `variantSpecOverrides` Object.values(variantSpecOverrides).includes(k)) { // This allows the variant spec to also have the content normalization applied to it // When the spec is a variant it will be in the form of ['base_spec_name', { props: { ... } }] // So the actual validator spec of the variant will be the second item in the array // We also need to shallow clone this to ensure we don't mutate the original spec spec = _objectSpread({}, spec[1]); } if (spec.props) { spec.props = _objectSpread({}, spec.props); if (spec.props.content) { // 'tableCell_content' => { type: 'array', items: [ ... ] } if ((0, _utils.isString)(spec.props.content)) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any spec.props.content = specs[spec.props.content]; } // ['inline', 'emoji'] if (Array.isArray(spec.props.content)) { /** * Flatten * * Input: * [ { type: 'array', items: [ 'tableHeader' ] }, { type: 'array', items: [ 'tableCell' ] } ] * * Output: * { type: 'array', items: [ [ 'tableHeader' ], [ 'tableCell' ] ] } */ spec.props.content = { type: 'array', items: (spec.props.content || []).map(function (arr) { return arr.items; }) }; } else { spec.props.content = _objectSpread({}, spec.props.content); } spec.props.content.items = spec.props.content.items // ['inline'] => [['emoji', 'hr', ...]] // ['media'] => [['media']] .map(function (item) { return (0, _utils.isString)(item) ? // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any Array.isArray(specs[item]) ? // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any specs[item] : [item] : item; }) // [['emoji', 'hr', 'inline_code']] => [['emoji', 'hr', ['text', { marks: {} }]]] .map(function (item) { return item.map(function (subItem) { return ( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any Array.isArray(specs[subItem]) ? // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any specs[subItem] : (0, _utils.isString)(subItem) ? subItem : // Now `NoMark` produces `items: []`, should be fixed in generator ['text', subItem] ); }) // Remove unsupported nodes & marks // Filter nodes .filter(function (subItem) { if (nodes) { // Node with overrides // ['mediaSingle', { props: { content: { items: [ 'media', 'caption' ] } }}] if (Array.isArray(subItem)) { var _subItem$; var isMainNodeSupported = nodes.indexOf(subItem[0]) > -1; if (isMainNodeSupported && (_subItem$ = subItem[1]) !== null && _subItem$ !== void 0 && (_subItem$ = _subItem$.props) !== null && _subItem$ !== void 0 && (_subItem$ = _subItem$.content) !== null && _subItem$ !== void 0 && _subItem$.items) { return subItem[1].props.content.items.every(function (item) { return nodes.indexOf(item) > -1; }); } return isMainNodeSupported; } return nodes.indexOf(subItem) > -1; } return true; }) // Filter marks .map(function (subItem) { return Array.isArray(subItem) && marks ? /** * TODO: Probably try something like immer, but it's 3.3kb gzipped. * Not worth it just for this. */ [subItem[0], mapMarksItems(subItem[1])] : subItem; }); }); } } newSpecs[k] = spec; return newSpecs; }, {}); } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any function getOptionsForType(type, list) { if (!list) { return {}; } for (var i = 0, len = list.length; i < len; i++) { var spec = list[i]; var name = spec; var options = {}; if (Array.isArray(spec)) { var _spec = (0, _slicedToArray2.default)(spec, 2); name = _spec[0]; options = _spec[1]; } if (name === type) { return options; } } return false; } var isValidatorSpecAttrs = function isValidatorSpecAttrs(spec) { return !!spec.props; }; function validateAttrs(spec, value) { if (!(0, _utils.isDefined)(value)) { return !!spec.optional; } if (isValidatorSpecAttrs(spec)) { // If spec has ".props" it is ValidatorSpecAttrs and need to pipe back in recursively var _partitionObject = partitionObject(spec.props, function (key, subSpec) { return ( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any validateAttrs(subSpec, value[key]) ); }), _partitionObject2 = (0, _slicedToArray2.default)(_partitionObject, 2), _ = _partitionObject2[0], invalidKeys = _partitionObject2[1]; return invalidKeys.length === 0; } // extension_node parameters has no type if (!(0, _utils.isDefined)(spec.type)) { return !!spec.optional; } switch (spec.type) { case 'boolean': return (0, _utils.isBoolean)(value); case 'number': return (0, _utils.isNumber)(value) && ((0, _utils.isDefined)(spec.minimum) ? spec.minimum <= value : true) && ((0, _utils.isDefined)(spec.maximum) ? spec.maximum >= value : true); case 'integer': return (0, _utils.isInteger)(value) && ((0, _utils.isDefined)(spec.minimum) ? spec.minimum <= value : true) && ((0, _utils.isDefined)(spec.maximum) ? spec.maximum >= value : true); case 'string': var validatorFnPassed = function validatorFnPassed(rule) { return typeof value === 'string' && (0, _utils.isDefined)(_rules.validatorFnMap[rule]) && _rules.validatorFnMap[rule](value); }; return (0, _utils.isString)(value) && ( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (0, _utils.isDefined)(spec.minLength) ? spec.minLength <= value.length : true) && ((0, _utils.isDefined)(spec.validatorFn) ? validatorFnPassed(spec.validatorFn) : true) && ( // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp spec.pattern ? new RegExp(spec.pattern).test(value) : true); case 'object': return (0, _utils.isPlainObject)(value); case 'array': if (Array.isArray(value)) { var isTuple = !!spec.isTupleLike; var minItems = spec.minItems, maxItems = spec.maxItems; if (minItems !== undefined && value.length < minItems || maxItems !== undefined && value.length > maxItems) { return false; } if (isTuple) { // If value has fewer items than tuple has specs - we are fine with that. var numberOfItemsToCheck = Math.min(spec.items.length, value.length); return Array(numberOfItemsToCheck).fill(null).every(function (_, i) { return validateAttrs(spec.items[i], value[i]); }); } else { return value.every(function (valueItem) { return ( // We check that at least one of the specs in the list (spec.items) matches each value from spec.items.some(function (itemSpec) { return validateAttrs(itemSpec, valueItem); }) ); }); } } return false; case 'enum': return (0, _utils.isString)(value) && spec.values.indexOf(value) > -1; } } var errorMessageFor = function errorMessageFor(type, message) { return "".concat(type, ": ").concat(message, "."); }; var getUnsupportedOptions = function getUnsupportedOptions(spec) { if (spec && spec.props && spec.props.content) { var _spec$props$content = spec.props.content, allowUnsupportedBlock = _spec$props$content.allowUnsupportedBlock, allowUnsupportedInline = _spec$props$content.allowUnsupportedInline; return { allowUnsupportedBlock: allowUnsupportedBlock, allowUnsupportedInline: allowUnsupportedInline }; } return {}; }; var invalidChildContent = function invalidChildContent(child, errorCallback, parentSpec) { var message = errorMessageFor(child.type, 'invalid content'); if (!errorCallback) { throw new Error(message); } else { var _parentSpec$props; return errorCallback(_objectSpread({}, child), { code: 'INVALID_CONTENT', message: message, meta: { parentType: parentSpec === null || parentSpec === void 0 || (_parentSpec$props = parentSpec.props) === null || _parentSpec$props === void 0 || (_parentSpec$props = _parentSpec$props.type) === null || _parentSpec$props === void 0 ? void 0 : _parentSpec$props.values[0] } }, getUnsupportedOptions(parentSpec)); } }; var unsupportedMarkContent = function unsupportedMarkContent(errorCode, mark, errorCallback, errorMessage) { var message = errorMessage || errorMessageFor(mark.type, 'unsupported mark'); if (!errorCallback) { throw new Error(message); } else { return errorCallback(_objectSpread({}, mark), { code: errorCode, message: message, meta: mark }, { allowUnsupportedBlock: false, allowUnsupportedInline: false, isMark: true }); } }; var unsupportedNodeAttributesContent = function unsupportedNodeAttributesContent(entity, errorCode, invalidAttributes, message, errorCallback) { if (!errorCallback) { throw new Error(message); } else { return errorCallback({ type: entity.type }, { code: errorCode, message: message, meta: invalidAttributes }, { allowUnsupportedBlock: false, allowUnsupportedInline: false, isMark: false, isNodeAttribute: true }); } }; /** * Map of base spec names to a preferred variant spec that should be used in their place during validation. * * WARNING: The variant spec must be a strict superset of the base spec, i.e. any content valid * under the base spec must also be valid under the variant */ var variantSpecOverrides = { listItem: 'listItem_with_flexible_first_child', taskList: 'taskList_with_flexible_first_child' }; /** * Replaces base validator specs with their designated variant overrides */ var applyVariantSpecOverrides = function applyVariantSpecOverrides(validatorSpecs) { Object.entries(variantSpecOverrides).forEach(function (_ref2) { var _ref3 = (0, _slicedToArray2.default)(_ref2, 2), base = _ref3[0], variant = _ref3[1]; var baseSpec = validatorSpecs[base]; var variantOverride = validatorSpecs[variant]; if (baseSpec !== null && baseSpec !== void 0 && baseSpec.props && variantOverride !== null && variantOverride !== void 0 && variantOverride.props && (0, _typeof2.default)(baseSpec.props) === 'object' && (0, _typeof2.default)(variantOverride.props) === 'object') { // Merge variant overrides INTO the base spec baseSpec.props = _objectSpread(_objectSpread({}, baseSpec.props), variantOverride.props); } }); }; function validator(nodes, marks, options) { var validatorSpecs = createSpec(nodes, marks); if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_schema', 'isEnabled', true)) { applyVariantSpecOverrides(validatorSpecs); } var _ref4 = options || {}, _ref4$mode = _ref4.mode, mode = _ref4$mode === void 0 ? 'strict' : _ref4$mode, _ref4$allowPrivateAtt = _ref4.allowPrivateAttributes, allowPrivateAttributes = _ref4$allowPrivateAtt === void 0 ? false : _ref4$allowPrivateAtt; var validate = function validate(entity, errorCallback, allowed, parentSpec) { if (!allowed) { var _iterator = _createForOfIteratorHelper((0, _extractAllowedContent.extractAllowedContent)(validatorSpecs, entity)), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var _allowed = _step.value; var _validationResult = validateNode(entity, errorCallback, _allowed, parentSpec); if (_validationResult.valid) { return { entity: _validationResult.entity, valid: _validationResult.valid }; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } // If `allowed` was provided or we haven't passed yet, return the initial result var validationResult = validateNode(entity, errorCallback, allowed, parentSpec); return { entity: validationResult.entity, valid: validationResult.valid }; }; var validateNode = function validateNode(entity, errorCallback, allowed, parentSpec) { var isMark = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var type = entity.type; var newEntity = _objectSpread({}, entity); var err = function err(code, msg, meta) { var message = errorMessageFor(type, msg); if (errorCallback) { return { valid: false, entity: errorCallback(newEntity, { code: code, message: message, meta: meta }, getUnsupportedOptions(parentSpec)) }; } else { throw new Error(message); } }; if (type) { var typeOptions = getOptionsForType(type, allowed); if (typeOptions === false) { return isMark ? { valid: false } : err('INVALID_TYPE', 'type not allowed here'); } var spec = validatorSpecs[type]; if (!spec) { return err('INVALID_TYPE', "".concat(type, ": No validation spec found for type!")); } var specBasedValidationResult = specBasedValidationFor(spec, typeOptions, entity, err, newEntity, type, errorCallback, isMark); if (specBasedValidationResult.hasValidated && specBasedValidationResult.result) { return specBasedValidationResult.result; } } else { return err('INVALID_TYPE', 'ProseMirror Node/Mark should contain a `type`'); } return { valid: true, entity: newEntity }; }; return validate; function marksValidationFor(validator, entity, errorCallback, newEntity, err) { var validationResult; if (validator.props && validator.props.marks) { var marksSet = allowedMarksFor(validator); var marksValidationResult = marksAfterValidation(entity, errorCallback, marksSet, validator); validationResult = { valid: true, entity: newEntity, marksValidationOutput: marksValidationResult }; } else { validationResult = marksForEntitySpecNotSupportingMarks(entity, newEntity, errorCallback, err); } return validationResult; } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any function validatorFor(spec, typeOptions) { return _objectSpread(_objectSpread(_objectSpread({}, spec), typeOptions), spec.props ? { props: _objectSpread(_objectSpread({}, spec.props), typeOptions['props'] || {}) } : {}); } function marksAfterValidation(entity, errorCallback, marksSet, validator) { return entity.marks ? entity.marks.map(function (mark) { var isAKnownMark = marks ? marks.indexOf(mark.type) > -1 : true; if (mode === 'strict' && isAKnownMark) { var finalResult = validateNode(mark, errorCallback, marksSet, validator, true); var finalMark = finalResult.entity; if (finalMark) { return { valid: true, originalMark: mark, newMark: finalMark }; } // this checks for mark level attribute errors // and propagates error code and message else if (finalResult.marksValidationOutput && finalResult.marksValidationOutput.length) { return { valid: false, originalMark: mark, errorCode: finalResult.marksValidationOutput[0].errorCode, message: finalResult.marksValidationOutput[0].message }; } else { return { valid: false, originalMark: mark, errorCode: 'INVALID_TYPE' }; } } else { return { valid: false, originalMark: mark, errorCode: 'INVALID_CONTENT' }; } }) : []; } function allowedMarksFor(validator) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var _ref5 = validator.props.marks, items = _ref5.items; var marksSet = items.length ? Array.isArray(items[0]) ? items[0] : items : []; return marksSet; } function marksForEntitySpecNotSupportingMarks(prevEntity, newEntity, errorCallback, err) { var errorCode = 'REDUNDANT_MARKS'; var currentMarks = prevEntity.marks || []; var newMarks = currentMarks.map(function (mark) { var isUnsupportedNodeAttributeMark = mark.type === 'unsupportedNodeAttribute'; if (isUnsupportedNodeAttributeMark) { return mark; } return unsupportedMarkContent(errorCode, mark, errorCallback); }); if (newMarks.length) { newEntity.marks = newMarks; return { valid: true, entity: newEntity }; } else { return err('REDUNDANT_MARKS', 'redundant marks', { marks: Object.keys(currentMarks) }); } } function requiredPropertyValidationFor(validatorSpec, prevEntity, err) { var result = { valid: true, entity: prevEntity }; if (validatorSpec.required) { if (!validatorSpec.required.every(function (prop) { return (0, _utils.isDefined)(prevEntity[prop]); })) { result = err('MISSING_PROPERTIES', 'required prop missing'); } } return result; } function textPropertyValidationFor(validatorSpec, prevEntity, err) { var result = { valid: true, entity: prevEntity }; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (validatorSpec.props.text) { if ((0, _utils.isDefined)(prevEntity.text) && // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion !validateAttrs(validatorSpec.props.text, prevEntity.text)) { result = err('INVALID_TEXT', "'text' validation failed"); } } return result; } function contentLengthValidationFor(validatorSpec, prevEntity, err) { var result = { valid: true, entity: prevEntity }; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (validatorSpec.props.content && prevEntity.content) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var _content = validatorSpec.props.content, minItems = _content.minItems, maxItems = _content.maxItems; var length = prevEntity.content.length; if ((0, _utils.isDefined)(minItems) && minItems > length) { result = err('INVALID_CONTENT_LENGTH', "'content' should have more than ".concat(minItems, " child"), { length: length, requiredLength: minItems, type: 'minimum' }); } else if ((0, _utils.isDefined)(maxItems) && maxItems < length) { result = err('INVALID_CONTENT_LENGTH', "'content' should have less than ".concat(maxItems, " child"), { length: length, requiredLength: maxItems, type: 'maximum' }); } } return result; } function invalidAttributesFor(validatorSpec, prevEntity) { var invalidAttrs = []; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var validatorAttrs = {}; if (validatorSpec.props && validatorSpec.props.attrs) { var attrOptions = (0, _utils.makeArray)(validatorSpec.props.attrs); /** * Attrs can be union type so try each path * attrs: [{ props: { url: { type: 'string' } } }, { props: { data: {} } }], * Gotcha: It will always report the last failure. */ for (var i = 0, length = attrOptions.length; i < length; ++i) { var attrOption = attrOptions[i]; if (attrOption && attrOption.props) { var _partitionObject3 = partitionObject(attrOption.props, function (key, spec) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var valueToValidate = prevEntity.attrs[key]; return validateAttrs(spec, valueToValidate); }); var _partitionObject4 = (0, _slicedToArray2.default)(_partitionObject3, 2); invalidAttrs = _partitionObject4[1]; } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion validatorAttrs = attrOption; if (!invalidAttrs.length) { break; } } } return { invalidAttrs: invalidAttrs, validatorAttrs: validatorAttrs }; } function attributesValidationFor(validatorSpec, prevEntity, newEntity, isMark, errorCallback) { var validatorSpecAllowsAttributes = validatorSpec.props && validatorSpec.props.attrs; if (prevEntity.attrs) { if (!validatorSpecAllowsAttributes) { if (isMark) { return handleNoAttibutesAllowedInSpecForMark(prevEntity, prevEntity.attrs); } var attrs = Object.keys(prevEntity.attrs); return handleUnsupportedNodeAttributes(prevEntity, newEntity, [], attrs, errorCallback); } var _validateAttributes = validateAttributes(validatorSpec, prevEntity, prevEntity.attrs), hasUnsupportedAttrs = _validateAttributes.hasUnsupportedAttrs, redundantAttrs = _validateAttributes.redundantAttrs, invalidAttrs = _validateAttributes.invalidAttrs; if (hasUnsupportedAttrs) { if (isMark) { return handleUnsupportedMarkAttributes(prevEntity, invalidAttrs, redundantAttrs); } return handleUnsupportedNodeAttributes(prevEntity, newEntity, invalidAttrs, redundantAttrs, errorCallback); } } return { valid: true, entity: prevEntity }; } function validateAttributes(validatorSpec, prevEntity, // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any attributes) { var invalidAttributesResult = invalidAttributesFor(validatorSpec, prevEntity); var invalidAttrs = invalidAttributesResult.invalidAttrs; var validatorAttrs = invalidAttributesResult.validatorAttrs; var attrs = Object.keys(attributes).filter(function (k) { return !(allowPrivateAttributes && k.startsWith('__')); }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var redundantAttrs = attrs.filter(function (a) { return !validatorAttrs.props[a]; }); var hasRedundantAttrs = redundantAttrs.length > 0; var hasUnsupportedAttrs = invalidAttrs.length || hasRedundantAttrs; return { hasUnsupportedAttrs: hasUnsupportedAttrs, invalidAttrs: invalidAttrs, redundantAttrs: redundantAttrs }; } function handleUnsupportedNodeAttributes(prevEntity, newEntity, invalidAttrs, redundantAttrs, errorCallback) { var attr = invalidAttrs.concat(redundantAttrs); var result = { valid: true, entity: prevEntity }; var message = errorMessageFor(prevEntity.type, "'attrs' validation failed"); var errorCode = 'UNSUPPORTED_ATTRIBUTES'; newEntity.marks = wrapUnSupportedNodeAttributes(prevEntity, newEntity, attr, errorCode, message, errorCallback); result = { valid: true, entity: newEntity }; return result; } function handleUnsupportedMarkAttributes(prevEntity, invalidAttrs, redundantAttrs) { var errorCode = 'INVALID_ATTRIBUTES'; var message = errorMessageFor(prevEntity.type, "'attrs' validation failed"); var hasRedundantAttrs = redundantAttrs.length; var hasBothInvalidAndRedundantAttrs = hasRedundantAttrs && invalidAttrs.length; if (!hasBothInvalidAndRedundantAttrs && hasRedundantAttrs) { errorCode = 'REDUNDANT_ATTRIBUTES'; message = errorMessageFor('redundant attributes found', redundantAttrs.join(', ')); } var markValidationResult = { valid: true, originalMark: prevEntity, errorCode: errorCode, message: message }; return { valid: false, marksValidationOutput: [markValidationResult] }; } function handleNoAttibutesAllowedInSpecForMark(prevEntity, // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any attributes) { var message = errorMessageFor('redundant attributes found', Object.keys(attributes).join(', ')); var errorCode = 'REDUNDANT_ATTRIBUTES'; var markValidationResult = { valid: true, originalMark: prevEntity, errorCode: errorCode, message: message }; return { valid: false, marksValidationOutput: [markValidationResult] }; } function wrapUnSupportedNodeAttributes(prevEntity, newEntity, invalidAttrs, errorCode, message, errorCallback) { var invalidValues = {}; // eslint-disable-next-line guard-for-in for (var invalidAttr in invalidAttrs) { invalidValues[invalidAttrs[invalidAttr]] = prevEntity.attrs && prevEntity.attrs[invalidAttrs[invalidAttr]]; if (newEntity.attrs) { delete newEntity.attrs[invalidAttrs[invalidAttr]]; } } var unsupportedNodeAttributeValues = unsupportedNodeAttributesContent(prevEntity, errorCode, invalidValues, message, errorCallback); var finalEntity = _objectSpread({}, newEntity); if (finalEntity.marks) { if (!unsupportedNodeAttributeValues) { return finalEntity.marks; } // If there is an existing unsupported node attribute mark, overwrite it to avoid duplicate marks var existingMark = finalEntity.marks.find(function (mark) { return mark.type === unsupportedNodeAttributeValues.type; }); if (existingMark) { existingMark.attrs = unsupportedNodeAttributeValues.attrs; } else { finalEntity.marks.push(unsupportedNodeAttributeValues); } return finalEntity.marks; } else { return [unsupportedNodeAttributeValues]; } } function extraPropsValidationFor(validatorSpec, prevEntity, err, newEntity, type) { var result = { valid: true, entity: prevEntity }; var _partitionObject5 = partitionObject(prevEntity, function (k) { return ( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any (0, _utils.isDefined)(validatorSpec.props[k]) ); }), _partitionObject6 = (0, _slicedToArray2.default)(_partitionObject5, 2), requiredProps = _partitionObject6[0], redundantProps = _partitionObject6[1]; if (redundantProps.length) { if (mode === 'loose') { newEntity = { type: type }; requiredProps.reduce(function (acc, p) { return (0, _utils.copy)(prevEntity, acc, p); }, newEntity); } else { if (!((redundantProps.indexOf('marks') > -1 || redundantProps.indexOf('attrs') > -1) && redundantProps.length === 1)) { return err('REDUNDANT_PROPERTIES', "redundant props found: ".concat(redundantProps.join(', ')), { props: redundantProps }); } } } return result; } function specBasedValidationFor(spec, // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any typeOptions, prevEntity, err, newEntity, type, errorCallback, isMark) { var specBasedValidationResult = { hasValidated: false }; var validatorSpec = validatorFor(spec, typeOptions); if (!validatorSpec) { return specBasedValidationResult; } // Required Props // For array format where `required` is an array var requiredPropertyValidatonResult = requiredPropertyValidationFor(validatorSpec, prevEntity, err); if (!requiredPropertyValidatonResult.valid) { return { hasValidated: true, result: requiredPropertyValidatonResult }; } if (!validatorSpec.props) { var props = Object.keys(prevEntity); // If there's no validator.props then there shouldn't be any key except `type` if (props.length > 1) { return { hasValidated: true, result: err('REDUNDANT_PROPERTIES', "redundant props found: ".concat(Object.keys(prevEntity).join(', ')), { props: props }) }; } return specBasedValidationResult; } // Check text var textPropertyValidationResult = textPropertyValidationFor(validatorSpec, prevEntity, err); if (!textPropertyValidationResult.valid) { return { hasValidated: true, result: textPropertyValidationResult }; } // Content Length var contentLengthValidationResult = contentLengthValidationFor(validatorSpec, prevEntity, err); if (!contentLengthValidationResult.valid) { return { hasValidated: true, result: contentLengthValidationResult }; } // Required Props // For object format based on `optional` property var _partitionObject7 = partitionObject(validatorSpec.props, function (k, v) { var _validatorSpec$requir; // if the validator is an array, then check // if the `required` field contains the key. var isOptional = Array.isArray(v) ? !((_validatorSpec$requir = validatorSpec.required) !== null && _validatorSpec$requir !== void 0 && _validatorSpec$requir.includes(k)) : (0, _typeof2.default)(v) === 'object' && v !== null && 'optional' in v ? v.optional : false; return isOptional || (0, _utils.isDefined)(prevEntity[k]); }), _partitionObject8 = (0, _slicedToArray2.default)(_partitionObject7, 2), missingProps = _partitionObject8[1]; if (missingProps.length) { return { hasValidated: true, result: err('MISSING_PROPERTIES', 'required prop missing', { props: missingProps }) }; } var attributesValidationResult = attributesValidationFor(validatorSpec, prevEntity, newEntity, isMark, errorCallback); if (!attributesValidationResult.valid) { return { hasValidated: true, result: attributesValidationResult }; } if (isMark && attributesValidationResult.valid) { return { hasValidated: true, result: attributesValidationResult }; } var extraPropsValidationResult = extraPropsValidationFor(validatorSpec, prevEntity, err, newEntity, type); if (!extraPropsValidationResult.valid) { return { hasValidated: true, result: extraPropsValidationResult }; } // Children if (validatorSpec.props.content) { var contentValidatorSpec = validatorSpec.props.content; if (prevEntity.content) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var validateChildNode = function validateChildNode(child, index) { if (child === undefined) { return child; } var validateChildMarks = function validateChildMarks(childEntity, marksValidationOutput, errorCallback, isLastValidationSpec) { var isParentTupleLike = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var marksAreValid = true; if (childEntity && childEntity.marks && marksValidationOutput) { var validMarks = marksValidationOutput.filter(function (mark) { return mark.valid; }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var finalMarks = marksValidationOutput.map(function (mr) { if (mr.valid) { return mr.newMark; } else { if (validMarks.length || isLastValidationSpec || isParentTupleLike || mr.errorCode === 'INVALID_TYPE' || mr.errorCode === 'INVALID_CONTENT' || mr.errorCode === 'REDUNDANT_ATTRIBUTES' || mr.errorCode === 'INVALID_ATTRIBUTES') { return unsupportedMarkContent( // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion mr.errorCode, mr.originalMark, errorCallback, mr.message); } return; } }).filter(Boolean); if (finalMarks.length) { childEntity.marks = finalMarks; } else { delete childEntity.marks; marksAreValid = false; } } return { valid: marksAreValid, entity: childEntity }; }; var hasMultipleCombinationOfContentAllowed = !!contentValidatorSpec.isTupleLike; if (hasMultipleCombinationOfContentAllowed) { var _validateNode = validateNode(child, errorCallback, (0, _utils.makeArray)(contentValidatorSpec.items[index] || contentValidatorSpec.items[contentValidatorSpec.items.length - 1]), validatorSpec), newChildEntity = _validateNode.entity, marksValidationOutput = _validateNode.marksValidationOutput; var _validateChildMarks = validateChildMarks(newChildEntity, marksValidationOutput, errorCallback, false, true), entity = _validateChildMarks.entity; return entity; } // Only go inside valid branch var allowedSpecsForEntity = contentValidatorSpec.items.filter(function (item) { return Array.isArray(item) ? item.some( // [p, hr, ...] or [p, [text, {}], ...] function (spec) { return (Array.isArray(spec) ? spec[0] : spec) === child.type; }) : true; }); if (allowedSpecsForEntity.length) { if (allowedSpecsForEntity.length > 1) { throw new Error('Consider using Tuple instead!'); } var maybeArray = (0, _utils.makeArray)(allowedSpecsForEntity[0]); var allowedSpecsForChild = maybeArray.filter(function (item) { return (Array.isArray(item) ? item[0] : item) === child.type; }); if (allowedSpecsForChild.length === 0) { return invalidChildContent(child, errorCallback, validatorSpec); } /** * When there's multiple possible branches try all of them. * If all of them fails, throw the first one. * e.g.- [['text', { marks: ['a'] }], ['text', { marks: ['b'] }]] */ var firstError; var firstChild; for (var i = 0, len = allowedSpecsForChild.length; i < len; i++) { try { var allowedValueForCurrentSpec = [allowedSpecsForChild[i]]; var _validateNode2 = validateNode(child, errorCallback, allowedValueForCurrentSpec, validatorSpec), valid = _validateNode2.valid, _newChildEntity = _validateNode2.entity, _marksValidationOutput = _validateNode2.marksValidationOutput; if (valid) { var isLastValidationSpec = i === allowedSpecsForChild.length - 1; var _validateChildMarks2 = validateChildMarks(_newChildEntity, _marksValidationOutput, errorCallback, isLastValidationSpec), marksAreValid = _validateChildMarks2.valid, _entity = _validateChildMarks2.entity; var unsupportedMarks = _entity && _entity.marks && _entity.marks.filter(function (mark) { return mark.type === 'unsupportedMark'; }) || []; if (marksAreValid && !unsupportedMarks.length) { return _entity; } else { firstChild = firstChild || _newChildEntity; } } else { firstChild = firstChild || _newChildEntity; } } catch (error) { firstError = firstError || error; } } if (!errorCallback) { throw firstError; } else { return firstChild; } } else { return invalidChildContent(child, errorCallback, validatorSpec); } }; newEntity.content = prevEntity.content.map(validateChildNode).filter(Boolean); } else if (!contentValidatorSpec.optional) { return { hasValidated: true, result: err('MISSING_PROPERTIES', 'missing `content` prop') }; } } // Marks if (prevEntity.marks) { return { hasValidated: true, result: marksValidationFor(validatorSpec, prevEntity, errorCallback, newEntity, err) }; } return specBasedValidationResult; } }