Skip to content
Permalink
Browse files

Refactor code to move logger handling to the validation function

  • Loading branch information...
gziolo committed Aug 27, 2019
1 parent 3c85086 commit 1232daec10882ec46a95c392c979b7da8aadab44
@@ -2,7 +2,7 @@
* External dependencies
*/
import { parse as hpqParse } from 'hpq';
import { flow, castArray, mapValues, omit, stubFalse } from 'lodash';
import { flow, get, castArray, mapValues, omit, stubFalse } from 'lodash';

/**
* WordPress dependencies
@@ -20,7 +20,7 @@ import {
getUnregisteredTypeHandlerName,
} from './registration';
import { createBlock } from './factory';
import { isValidBlockContent, logger } from './validation';
import { getBlockContentValidationResult } from './validation';
import { getCommentDelimitedContent, getSaveContent } from './serializer';
import { attr, html, text, query, node, children, prop } from './matchers';
import { normalizeBlockType } from './utils';
@@ -336,21 +336,23 @@ export function getMigratedBlock( block, parsedAttributes ) {
);

// Ignore the deprecation if it produces a block which is not valid.
const isValid = isValidBlockContent(
const { isValid, validationIssues } = getBlockContentValidationResult(
deprecatedBlockType,
migratedAttributes,
originalContent
);

if ( ! isValid ) {
block = {
...block,
validationIssues: [
...get( block, 'validationIssues', [] ),
...validationIssues,
],
};
continue;
}

block = {
...block,
isValid: true,
};

let migratedInnerBlocks = innerBlocks;

// A block may provide custom behavior to assign new attributes and/or
@@ -363,8 +365,12 @@ export function getMigratedBlock( block, parsedAttributes ) {
] = castArray( migrate( migratedAttributes, innerBlocks ) ) );
}

block.attributes = migratedAttributes;
block.innerBlocks = migratedInnerBlocks;
block = {
...block,
attributes: migratedAttributes,
innerBlocks: migratedInnerBlocks,
isValid: true,
};
}

return block;
@@ -378,8 +384,6 @@ export function getMigratedBlock( block, parsedAttributes ) {
* @return {?Object} An initialized block object (if possible).
*/
export function createBlockWithFallback( blockNode ) {
logger.startTransaction();

const { blockName: originalName } = blockNode;
let {
attrs: attributes,
@@ -486,7 +490,9 @@ export function createBlockWithFallback( blockNode ) {
// provided source value with the serialized output before there are any modifications to
// the block. When both match, the block is marked as valid.
if ( ! isFallbackBlock ) {
block.isValid = isValidBlockContent( blockType, block.attributes, innerHTML );
const { isValid, validationIssues } = getBlockContentValidationResult( blockType, block.attributes, innerHTML );
block.isValid = isValid;
block.validationIssues = validationIssues;
}

// Preserve original content for future use in case the block is parsed
@@ -495,8 +501,8 @@ export function createBlockWithFallback( blockNode ) {

block = getMigratedBlock( block, attributes );

if ( block.isValid ) {
if ( logger.hasQueuedItems() ) {
if ( block.validationIssues && block.validationIssues.length > 0 ) {
if ( block.isValid ) {
// eslint-disable-next-line no-console
console.info(
'Block successfully updated for `%s` (%o).\n\nNew content generated by `save` function:\n\n%s\n\nContent retrieved from post body:\n\n%s',
@@ -505,10 +511,9 @@ export function createBlockWithFallback( blockNode ) {
getSaveContent( blockType, block.attributes ),
block.originalContent
);
} else {
block.validationIssues.forEach( ( { log, args } ) => log( ...args ) );
}
logger.rollback();
} else {
logger.commit();
}

return block;
@@ -484,9 +484,7 @@ describe( 'block parser', () => {

const migratedBlock = getMigratedBlock( block, parsedAttributes );

expect( migratedBlock ).toBe( block );
expect( console ).toHaveErrored();
expect( console ).toHaveWarned();
expect( migratedBlock ).toEqual( expect.objectContaining( block ) );
} );

it( 'should return with attributes parsed by the deprecated version', () => {
@@ -19,7 +19,7 @@ import { decodeEntities } from '@wordpress/html-entities';
/**
* Internal dependencies
*/
import logger from './logger';
import { createLogger, createQueuedLogger } from './logger';
import { getSaveContent } from '../serializer';
import { normalizeBlockType } from '../utils';

@@ -299,10 +299,11 @@ export function getMeaningfulAttributePairs( token ) {
*
* @param {Object} actual Actual token.
* @param {Object} expected Expected token.
* @param {Object} logger Validation logger object.
*
* @return {boolean} Whether two text tokens are equivalent.
*/
export function isEquivalentTextTokens( actual, expected ) {
export function isEquivalentTextTokens( actual, expected, logger = createLogger() ) {
// This function is intentionally written as syntactically "ugly" as a hot
// path optimization. Text is progressively normalized in order from least-
// to-most operationally expensive, until the earliest point at which text
@@ -393,10 +394,11 @@ export const isEqualAttributesOfName = {
*
* @param {Array[]} actual Actual attributes tuples.
* @param {Array[]} expected Expected attributes tuples.
* @param {Object} logger Validation logger object.
*
* @return {boolean} Whether attributes are equivalent.
*/
export function isEqualTagAttributePairs( actual, expected ) {
export function isEqualTagAttributePairs( actual, expected, logger = createLogger() ) {
// Attributes is tokenized as tuples. Their lengths should match. This also
// avoids us needing to check both attributes sets, since if A has any keys
// which do not exist in B, we know the sets to be different.
@@ -441,14 +443,15 @@ export function isEqualTagAttributePairs( actual, expected ) {
* @type {Object}
*/
export const isEqualTokensOfType = {
StartTag: ( actual, expected ) => {
StartTag: ( actual, expected, logger = createLogger() ) => {
if ( actual.tagName !== expected.tagName ) {
logger.warning( 'Expected tag name `%s`, instead saw `%s`.', expected.tagName, actual.tagName );
return false;
}

return isEqualTagAttributePairs(
...[ actual, expected ].map( getMeaningfulAttributePairs )
...[ actual, expected ].map( getMeaningfulAttributePairs ),
logger
);
},
Chars: isEquivalentTextTokens,
@@ -482,11 +485,12 @@ export function getNextNonWhitespaceToken( tokens ) {
* Tokenize an HTML string, gracefully handling any errors thrown during
* underlying tokenization.
*
* @param {string} html HTML string to tokenize.
* @param {string} html HTML string to tokenize.
* @param {Object} logger Validation logger object.
*
* @return {Object[]|null} Array of valid tokenized HTML elements, or null on error
*/
function getHTMLTokens( html ) {
function getHTMLTokens( html, logger = createLogger() ) {
try {
return new Tokenizer( new DecodeEntityParser() ).tokenize( html );
} catch ( e ) {
@@ -523,14 +527,17 @@ export function isClosedByToken( currentToken, nextToken ) {
* false otherwise. Invalid HTML is not considered equivalent, even if the
* strings directly match.
*
* @param {string} actual Actual HTML string.
* @param {string} actual Actual HTML string.
* @param {string} expected Expected HTML string.
* @param {Object} logger Validation logger object.
*
* @return {boolean} Whether HTML strings are equivalent.
*/
export function isEquivalentHTML( actual, expected ) {
export function isEquivalentHTML( actual, expected, logger = createLogger() ) {
// Tokenize input content and reserialized save content
const [ actualTokens, expectedTokens ] = [ actual, expected ].map( getHTMLTokens );
const [ actualTokens, expectedTokens ] = [ actual, expected ].map(
( html ) => getHTMLTokens( html, logger )
);

// If either is malformed then stop comparing - the strings are not equivalent
if ( ! actualTokens || ! expectedTokens ) {
@@ -556,7 +563,7 @@ export function isEquivalentHTML( actual, expected ) {
// Defer custom token type equality handling, otherwise continue and
// assume as equal
const isEqualTokens = isEqualTokensOfType[ actualToken.type ];
if ( isEqualTokens && ! isEqualTokens( actualToken, expectedToken ) ) {
if ( isEqualTokens && ! isEqualTokens( actualToken, expectedToken, logger ) ) {
return false;
}

@@ -584,29 +591,33 @@ export function isEquivalentHTML( actual, expected ) {
}

/**
* Returns true if the parsed block is valid given the input content. A block
* is considered valid if, when serialized with assumed attributes, the content
* matches the original value.
*
* Logs to console in development environments when invalid.
* Returns an object with `isValid` property set to `true` if the parsed block
* is valid given the input content. A block is considered valid if, when serialized
* with assumed attributes, the content matches the original value. If block is
* invalid, this function returns all validations issues as well.
*
* @param {string|Object} blockTypeOrName Block type.
* @param {Object} attributes Parsed block attributes.
* @param {string} originalBlockContent Original block content.
* @param {Object} logger Validation logger object.
*
* @return {boolean} Whether block is valid.
* @return {Object} Whether block is valid and contains validation messages.
*/
export function isValidBlockContent( blockTypeOrName, attributes, originalBlockContent ) {
export function getBlockContentValidationResult( blockTypeOrName, attributes, originalBlockContent, logger = createQueuedLogger() ) {
const blockType = normalizeBlockType( blockTypeOrName );
let generatedBlockContent;
try {
generatedBlockContent = getSaveContent( blockType, attributes );
} catch ( error ) {
logger.error( 'Block validation failed because an error occurred while generating block content:\n\n%s', error.toString() );
return false;

return {
isValid: false,
validationIssues: logger.getItems(),
};
}

const isValid = isEquivalentHTML( originalBlockContent, generatedBlockContent );
const isValid = isEquivalentHTML( originalBlockContent, generatedBlockContent, logger );
if ( ! isValid ) {
logger.error(
'Block validation failed for `%s` (%o).\n\nContent generated by `save` function:\n\n%s\n\nContent retrieved from post body:\n\n%s',
@@ -617,7 +628,27 @@ export function isValidBlockContent( blockTypeOrName, attributes, originalBlockC
);
}

return isValid;
return {
isValid,
validationIssues: logger.getItems(),
};
}

export { logger };
/**
* Returns true if the parsed block is valid given the input content. A block
* is considered valid if, when serialized with assumed attributes, the content
* matches the original value.
*
* Logs to console in development environments when invalid.
*
* @param {string|Object} blockTypeOrName Block type.
* @param {Object} attributes Parsed block attributes.
* @param {string} originalBlockContent Original block content.
*
* @return {boolean} Whether block is valid.
*/
export function isValidBlockContent( blockTypeOrName, attributes, originalBlockContent ) {
const { isValid } = getBlockContentValidationResult( blockTypeOrName, attributes, originalBlockContent, createLogger() );

return isValid;
}

0 comments on commit 1232dae

Please sign in to comment.
You can’t perform that action at this time.