docs/lib/remove-liquid-statements.js

266 строки
10 KiB
JavaScript

const { drop, dropRight, first, last } = require('lodash')
// ----- START LIQUID PATTERNS ----- //
const startTag = /(?:^ *?)?{%-?/
const endTag = /-?%}/
const contentRegex = /[\s\S]*?/gm
const emptyString = /^\s*?$/m
const nonEmptyString = /^.*?\S.*?/m
const ifStatement = new RegExp(startTag.source + ' if .*?' + endTag.source)
const ifRegex = new RegExp(ifStatement.source, 'gm')
const firstIfRegex = new RegExp(ifStatement.source, 'm')
const elseRegex = new RegExp(startTag.source + ' else ?' + endTag.source)
const endRegex = new RegExp(startTag.source + ' endif ?' + endTag.source, 'g')
const captureEndRegex = new RegExp('(' + endRegex.source + ')', 'g')
const dropSecondEndIf = new RegExp('(' + endRegex.source + contentRegex.source + ')' + endRegex.source, 'm')
const inlineEndRegex = new RegExp(nonEmptyString.source + endRegex.source, 'gm')
const inlineIfRegex = new RegExp(nonEmptyString.source + ifStatement.source, 'gm')
// include one level of nesting
const liquidBlockRegex = new RegExp(ifRegex.source + '((' + ifRegex.source + contentRegex.source + endRegex.source + '|[\\s\\S])*?)' + endRegex.source, 'gm')
const elseBlockRegex = new RegExp(elseRegex.source + '(?:' + ifRegex.source + contentRegex.source + endRegex.source + '|[\\s\\S])*?' + endRegex.source, 'gm')
// ----- END LIQUID PATTERNS ----- //
module.exports = function removeLiquidStatements (content, versionToDeprecate, nextOldestVersion) {
// see tests/fixtures/remove-liquid-statements for examples
const regexes = {
// remove liquid only
greaterThanVersionToDeprecate: new RegExp(startTag.source + ` if ?(?: currentVersion == ('|")'?free-pro-team@latest'?('|") ?or)? currentVersion ver_gt ('|")${versionToDeprecate}('|") ` + endTag.source, 'gm'),
andGreaterThanVersionToDeprecate1: new RegExp('(' + startTag.source + ` if .*?) and currentVersion ver_gt (?:'|")${versionToDeprecate}(?:'|")( ` + endTag.source + ')', 'gm'),
andGreaterThanVersionToDeprecate2: new RegExp('(' + startTag.source + ` if )currentVersion ver_gt (?:'|")${versionToDeprecate}(?:'|") and (.*? ` + endTag.source + ')', 'gm'),
notEqualsVersionToDeprecate: new RegExp('(' + startTag.source + ` if)(?:( currentVersion .*?) or)? currentVersion != (?:'|")${versionToDeprecate}(?:'|")( ` + endTag.source + ')', 'gm'),
andNotEqualsVersionToDeprecate: new RegExp('(' + startTag.source + ` if .*?) and currentVersion != (?:'|")${versionToDeprecate}(?:'|")( ` + endTag.source + ')', 'gm'),
// remove liquid and content
lessThanNextOldestVersion: new RegExp(startTag.source + ` if .*?ver_lt ('|")${nextOldestVersion}('|") ?` + endTag.source, 'm'),
equalsVersionToDeprecate: new RegExp(startTag.source + ` if .*?== ('|")${versionToDeprecate}('|") ?` + endTag.source, 'm')
}
let allLiquidBlocks = getLiquidBlocks(content)
if (!allLiquidBlocks) return content
// remove markup like ver_gt "2.13" and leave content
content = removeLiquidOnly(content, allLiquidBlocks, regexes)
// get latest liquidBlocks based on updated content
allLiquidBlocks = getLiquidBlocks(content)
if (allLiquidBlocks) {
// remove content and surrounding markup like ver_lt "2.14" and == "2.13"
content = removeLiquidAndContent(content, allLiquidBlocks, regexes)
}
// clean up
content = removeExtraNewlines(content)
return content
}
function getLiquidBlocks (content) {
const liquidBlocks = content.match(liquidBlockRegex)
if (!liquidBlocks) return
// handle up to one level of nesting
const innerBlocks = getInnerBlocks(liquidBlocks)
return innerBlocks ? liquidBlocks.concat(innerBlocks) : liquidBlocks
}
function getInnerBlocks (liquidBlocks) {
const innerBlocks = []
liquidBlocks.forEach(block => {
const ifStatements = block.match(ifRegex)
if (!ifStatements) return
// only process blocks that have more than one if statement
if (!(ifStatements.length > 1)) return
// drop first character so we don't match outer if statement
// because it's already included in array of blocks
const newBlock = block.slice(1)
const innerIfStatements = newBlock.match(liquidBlockRegex)
// add each inner if statement to array of blocks
innerIfStatements.forEach(innerIfStatement => {
innerBlocks.push(innerIfStatement)
})
})
return innerBlocks
}
function removeLiquidOnly (content, allLiquidBlocks, regexes) {
const blocksToUpdate = allLiquidBlocks
.filter(block => {
// inner blocks are processed separately, so we only care about first if statements
const firstIf = block.match(firstIfRegex)
if (block.match(regexes.greaterThanVersionToDeprecate)) return firstIf[0] === block.match(regexes.greaterThanVersionToDeprecate)[0]
if (block.match(regexes.andGreaterThanVersionToDeprecate1)) return firstIf[0] === block.match(regexes.andGreaterThanVersionToDeprecate1)[0]
if (block.match(regexes.andGreaterThanVersionToDeprecate2)) return firstIf[0] === block.match(regexes.andGreaterThanVersionToDeprecate2)[0]
if (block.match(regexes.notEqualsVersionToDeprecate)) return firstIf[0] === block.match(regexes.notEqualsVersionToDeprecate)[0]
if (block.match(regexes.andNotEqualsVersionToDeprecate)) return firstIf[0] === block.match(regexes.andNotEqualsVersionToDeprecate)[0]
return false
})
blocksToUpdate.forEach(block => {
let newBlock = block
if (newBlock.match(regexes.andGreaterThanVersionToDeprecate1)) {
newBlock = newBlock.replace(regexes.andGreaterThanVersionToDeprecate1, matchAndStatement1)
}
if (newBlock.match(regexes.andGreaterThanVersionToDeprecate2)) {
newBlock = newBlock.replace(regexes.andGreaterThanVersionToDeprecate2, matchAndStatement2)
}
if (newBlock.match(regexes.notEqualsVersionToDeprecate)) {
newBlock = newBlock.replace(regexes.notEqualsVersionToDeprecate, matchNotEqualsStatement)
}
if (newBlock.match(regexes.andNotEqualsVersionToDeprecate)) {
newBlock = newBlock.replace(regexes.andNotEqualsVersionToDeprecate, matchAndNotEqualsStatement)
}
// replace else block with endif
const elseBlock = getElseBlock(newBlock)
if (elseBlock) {
newBlock = newBlock.replace(`${elseBlock}`, '{% endif %}')
}
if (newBlock.match(regexes.greaterThanVersionToDeprecate) || newBlock.match(regexes.notEqualsVersionToDeprecate)) {
newBlock = newBlock.replace(liquidBlockRegex, matchGreaterThan)
}
// we need more context to determine whether an if tag is inline or not
const startIndex = content.indexOf(block)
const endIndex = startIndex + block.length
const leftIndex = startIndex - 10
const blockWithLeftContext = content.slice(leftIndex, endIndex)
// only remove newlines around non-inline tags
if (blockWithLeftContext.match(inlineIfRegex) && block.match(inlineEndRegex)) {
newBlock = removeLastNewline(newBlock)
} else if (blockWithLeftContext.match(inlineIfRegex) && !block.match(inlineEndRegex)) {
newBlock = removeLastNewline(newBlock)
} else if (!blockWithLeftContext.match(inlineIfRegex) && block.match(inlineEndRegex)) {
newBlock = removeFirstNewline(newBlock)
} else {
newBlock = removeFirstAndLastNewlines(newBlock)
}
// final replacement
content = content.replace(block, newBlock)
})
return content
}
function matchGreaterThan (match, p1) {
return p1
}
function matchAndStatement1 (match, p1, p2) {
return p1 + p2
}
function matchAndStatement2 (match, p1, p2) {
return p1 + p2
}
function matchNotEqualsStatement (match, p1, p2, p3) {
if (!p2) return match
return p1 + p2 + p3
}
function matchAndNotEqualsStatement (match, p1, p2) {
return p1 + p2
}
function removeLiquidAndContent (content, allLiquidBlocks, regexes) {
const blocksToRemove = allLiquidBlocks
.filter(block => {
const firstIf = block.match(firstIfRegex)
if (block.match(regexes.lessThanNextOldestVersion)) return firstIf[0] === block.match(regexes.lessThanNextOldestVersion)[0]
if (block.match(regexes.equalsVersionToDeprecate)) return firstIf[0] === block.match(regexes.equalsVersionToDeprecate)[0]
return false
})
blocksToRemove.forEach(block => {
const elseBlock = getElseBlock(block)
// remove else conditionals but leave content
if (elseBlock) {
const numberOfEndTags = elseBlock[0].match(captureEndRegex)
// remove the endif as part of removing the else block
// if there are two endifs block, only remove the second one
// this applies specifically to example8 in less-than-next-oldest fixture
let newBlock
if (numberOfEndTags.length > 1) {
const blockWithFirstElseRemoved = elseBlock[0].replace(elseRegex, '')
newBlock = blockWithFirstElseRemoved.replace(dropSecondEndIf, '$1')
} else {
newBlock = elseBlock[0].replace(elseRegex, '').replace(captureEndRegex, '')
}
content = content.replace(block, newBlock.trim())
} else {
content = content.replace(block, '')
}
})
return content
}
function removeFirstNewline (block) {
const lines = block.split(/\r?\n/)
if (!first(lines).match(emptyString)) return block
return drop(lines, 1).join('\n')
}
function removeLastNewline (block) {
const lines = block.split(/\r?\n/)
if (!last(lines).match(emptyString)) return block
return dropRight(lines, 1).join('\n')
}
function removeFirstAndLastNewlines (block) {
block = removeFirstNewline(block)
return removeLastNewline(block)
}
function getElseBlock (block) {
const firstIf = block.match(firstIfRegex)
const elseBlock = block.match(elseBlockRegex)
// if no else block, return the null result
if (!elseBlock) return elseBlock
// if there is an else block, check for an inner block
const blockWithFirstIfRemoved = block.replace(firstIf[0], '')
const innerBlock = blockWithFirstIfRemoved.match(liquidBlockRegex)
// if no inner block, we can safely return the else block as is
if (!innerBlock) return elseBlock
// if there is an inner block, check if it is contained by the else block
// if so, we can safely return the else block as is
// see example8 in less-than-next-oldest
if (elseBlock[0].includes(innerBlock[0])) return block.match(elseBlockRegex)
// if the inner block contains the else block,
// drop the inner block and return any other else blocks
// see example7 in greater-than fixtures
if (innerBlock[0].includes(elseBlock[0])) {
const newBlock = block.replace(innerBlock[0], '')
return newBlock.match(elseBlockRegex)
}
// otherwise, return any else matches on the original block
return block.match(elseBlockRegex)
}
function removeExtraNewlines (content) {
return content.replace(/(\r?\n){3,4}/gm, '\n\n')
}