remark-parse-no-trim
Version:
remark plugin to parse Markdown
446 lines (359 loc) • 9.72 kB
JavaScript
'use strict'
var repeat = require('repeat-string')
var decimal = require('is-decimal')
var getIndent = require('../util/get-indentation')
var removeIndent = require('../util/remove-indentation')
var interrupt = require('../util/interrupt')
module.exports = list
var asterisk = '*'
var underscore = '_'
var plusSign = '+'
var dash = '-'
var dot = '.'
var space = ' '
var lineFeed = '\n'
var tab = '\t'
var rightParenthesis = ')'
var lowercaseX = 'x'
var tabSize = 4
var looseListItemExpression = /\n\n(?!\s*$)/
var taskItemExpression = /^\[([ X\tx])][ \t]/
var bulletExpression = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t|$|(?=\n))([^\n]*)/
var pedanticBulletExpression = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/
var initialIndentExpression = /^( {1,4}|\t)?/gm
function list(eat, value, silent) {
var self = this
var commonmark = self.options.commonmark
var pedantic = self.options.pedantic
var tokenizers = self.blockTokenizers
var interuptors = self.interruptList
var index = 0
var length = value.length
var start = null
var size
var queue
var ordered
var character
var marker
var nextIndex
var startIndex
var prefixed
var currentMarker
var content
var line
var previousEmpty
var empty
var items
var allLines
var emptyLines
var item
var enterTop
var exitBlockquote
var spread = false
var node
var now
var end
var indented
while (index < length) {
character = value.charAt(index)
if (character !== tab && character !== space) {
break
}
index++
}
character = value.charAt(index)
if (character === asterisk || character === plusSign || character === dash) {
marker = character
ordered = false
} else {
ordered = true
queue = ''
while (index < length) {
character = value.charAt(index)
if (!decimal(character)) {
break
}
queue += character
index++
}
character = value.charAt(index)
if (
!queue ||
!(character === dot || (commonmark && character === rightParenthesis))
) {
return
}
/* Slightly abusing `silent` mode, whose goal is to make interrupting
* paragraphs work.
* Well, that’s exactly what we want to do here: don’t interrupt:
* 2. here, because the “list” doesn’t start with `1`. */
if (silent && queue !== '1') {
return
}
start = parseInt(queue, 10)
marker = character
}
character = value.charAt(++index)
if (
character !== space &&
character !== tab &&
(pedantic || (character !== lineFeed && character !== ''))
) {
return
}
if (silent) {
return true
}
index = 0
items = []
allLines = []
emptyLines = []
while (index < length) {
nextIndex = value.indexOf(lineFeed, index)
startIndex = index
prefixed = false
indented = false
if (nextIndex === -1) {
nextIndex = length
}
size = 0
while (index < length) {
character = value.charAt(index)
if (character === tab) {
size += tabSize - (size % tabSize)
} else if (character === space) {
size++
} else {
break
}
index++
}
if (item && size >= item.indent) {
indented = true
}
character = value.charAt(index)
currentMarker = null
if (!indented) {
if (
character === asterisk ||
character === plusSign ||
character === dash
) {
currentMarker = character
index++
size++
} else {
queue = ''
while (index < length) {
character = value.charAt(index)
if (!decimal(character)) {
break
}
queue += character
index++
}
character = value.charAt(index)
index++
if (
queue &&
(character === dot || (commonmark && character === rightParenthesis))
) {
currentMarker = character
size += queue.length + 1
}
}
if (currentMarker) {
character = value.charAt(index)
if (character === tab) {
size += tabSize - (size % tabSize)
index++
} else if (character === space) {
end = index + tabSize
while (index < end) {
if (value.charAt(index) !== space) {
break
}
index++
size++
}
if (index === end && value.charAt(index) === space) {
index -= tabSize - 1
size -= tabSize - 1
}
} else if (character !== lineFeed && character !== '') {
currentMarker = null
}
}
}
if (currentMarker) {
if (!pedantic && marker !== currentMarker) {
break
}
prefixed = true
} else {
if (!commonmark && !indented && value.charAt(startIndex) === space) {
indented = true
} else if (commonmark && item) {
indented = size >= item.indent || size > tabSize
}
prefixed = false
index = startIndex
}
line = value.slice(startIndex, nextIndex)
content = startIndex === index ? line : value.slice(index, nextIndex)
if (
currentMarker === asterisk ||
currentMarker === underscore ||
currentMarker === dash
) {
if (tokenizers.thematicBreak.call(self, eat, line, true)) {
break
}
}
previousEmpty = empty
empty = !prefixed && !content.trim().length
if (indented && item) {
item.value = item.value.concat(emptyLines, line)
allLines = allLines.concat(emptyLines, line)
emptyLines = []
} else if (prefixed) {
if (emptyLines.length !== 0) {
spread = true
item.value.push('')
item.trail = emptyLines.concat()
}
item = {
value: [line],
indent: size,
trail: []
}
items.push(item)
allLines = allLines.concat(emptyLines, line)
emptyLines = []
} else if (empty) {
if (previousEmpty && !commonmark) {
break
}
emptyLines.push(line)
} else {
if (previousEmpty) {
break
}
if (interrupt(interuptors, tokenizers, self, [eat, line, true])) {
break
}
item.value = item.value.concat(emptyLines, line)
allLines = allLines.concat(emptyLines, line)
emptyLines = []
}
index = nextIndex + 1
}
node = eat(allLines.join(lineFeed)).reset({
type: 'list',
ordered: ordered,
start: start,
spread: spread,
children: []
})
enterTop = self.enterList()
exitBlockquote = self.enterBlock()
index = -1
length = items.length
while (++index < length) {
item = items[index].value.join(lineFeed)
now = eat.now()
eat(item)(listItem(self, item, now), node)
item = items[index].trail.join(lineFeed)
if (index !== length - 1) {
item += lineFeed
}
eat(item)
}
enterTop()
exitBlockquote()
return node
}
function listItem(ctx, value, position) {
var offsets = ctx.offset
var fn = ctx.options.pedantic ? pedanticListItem : normalListItem
var checked = null
var task
var indent
value = fn.apply(null, arguments)
if (ctx.options.gfm) {
task = value.match(taskItemExpression)
if (task) {
indent = task[0].length
checked = task[1].toLowerCase() === lowercaseX
offsets[position.line] += indent
value = value.slice(indent)
}
}
return {
type: 'listItem',
spread: looseListItemExpression.test(value),
checked: checked,
children: ctx.tokenizeBlock(value, position)
}
}
// Create a list-item using overly simple mechanics.
function pedanticListItem(ctx, value, position) {
var offsets = ctx.offset
var line = position.line
// Remove the list-item’s bullet.
value = value.replace(pedanticBulletExpression, replacer)
// The initial line was also matched by the below, so we reset the `line`.
line = position.line
return value.replace(initialIndentExpression, replacer)
// A simple replacer which removed all matches, and adds their length to
// `offset`.
function replacer($0) {
offsets[line] = (offsets[line] || 0) + $0.length
line++
return ''
}
}
// Create a list-item using sane mechanics.
function normalListItem(ctx, value, position) {
var offsets = ctx.offset
var line = position.line
var max
var bullet
var rest
var lines
var trimmedLines
var index
var length
// Remove the list-item’s bullet.
value = value.replace(bulletExpression, replacer)
lines = value.split(lineFeed)
trimmedLines = removeIndent(value, getIndent(max).indent).split(lineFeed)
// We replaced the initial bullet with something else above, which was used
// to trick `removeIndentation` into removing some more characters when
// possible. However, that could result in the initial line to be stripped
// more than it should be.
trimmedLines[0] = rest
offsets[line] = (offsets[line] || 0) + bullet.length
line++
index = 0
length = lines.length
while (++index < length) {
offsets[line] =
(offsets[line] || 0) + lines[index].length - trimmedLines[index].length
line++
}
return trimmedLines.join(lineFeed)
/* eslint-disable-next-line max-params */
function replacer($0, $1, $2, $3, $4) {
bullet = $1 + $2 + $3
rest = $4
// Make sure that the first nine numbered list items can indent with an
// extra space. That is, when the bullet did not receive an extra final
// space.
if (Number($2) < 10 && bullet.length % 2 === 1) {
$2 = space + $2
}
max = $1 + repeat(space, $2.length) + $3
return max + rest
}
}