UNPKG

eslint-plugin-unicorn

Version:
157 lines (128 loc) • 3.58 kB
import {decodeHTMLAttribute} from 'entities'; const maximumNamedHtmlCharacterReferenceLength = 64; const decimalDigitPattern = /^\d$/u; const hexadecimalDigitPattern = /^[\da-f]$/iu; const isSrcsetWhitespace = character => /^[\t\n\f\r ]$/u.test(character); function getNumericCharacterReferenceEnd(value, index) { let end = index + 2; const isHexadecimal = value[end]?.toLowerCase() === 'x'; const digitPattern = isHexadecimal ? hexadecimalDigitPattern : decimalDigitPattern; if (isHexadecimal) { end++; } const digitStart = end; while (digitPattern.test(value[end])) { end++; } if (end === digitStart) { return; } return value[end] === ';' ? end + 1 : end; } function getSrcsetCharacter(value, index) { if (value[index] !== '&') { return { value: value[index], end: index + 1, }; } let end; if (value[index + 1] === '#') { end = getNumericCharacterReferenceEnd(value, index); } else { // A named character reference is `&` + letters and digits + `;`. Anything else is a literal `&` followed by text, taking the first `;` of a longer span would swallow the rest of the URL. const possibleCharacterReference = value.slice(index, index + maximumNamedHtmlCharacterReferenceLength); const match = /^&[\da-z]+;/i.exec(possibleCharacterReference); end = match ? index + match[0].length : undefined; } if (end === undefined) { return { value: value[index], end: index + 1, }; } const source = value.slice(index, end); const decodedValue = decodeHTMLAttribute(source); if (decodedValue === source) { return { value: value[index], end: index + 1, }; } return { value: decodedValue, end, }; } function skipSrcsetSeparators(value, index) { while (index < value.length) { const character = getSrcsetCharacter(value, index); if (character.value !== ',' && !isSrcsetWhitespace(character.value)) { return index; } index = character.end; } return value.length; } function getSrcsetCandidate(value, index) { let end = index; let trailingCommaStart; while (index < value.length) { const character = getSrcsetCharacter(value, index); if (isSrcsetWhitespace(character.value)) { return { end: trailingCommaStart ?? end, next: index, hasTrailingComma: trailingCommaStart !== undefined, }; } if (character.value === ',') { trailingCommaStart ??= index; } else { trailingCommaStart = undefined; } end = character.end; index = character.end; } return { end: trailingCommaStart ?? end, next: index, hasTrailingComma: trailingCommaStart !== undefined, }; } function getNextSrcsetCandidateStart(value, index) { let parenthesisDepth = 0; while (index < value.length) { const character = getSrcsetCharacter(value, index); index = character.end; if (character.value === '(') { parenthesisDepth++; } else if (character.value === ')' && parenthesisDepth > 0) { parenthesisDepth--; } else if (character.value === ',' && parenthesisDepth === 0) { return index; } } return value.length; } export default function getSrcsetCandidates(value) { const candidates = []; let index = 0; while (index < value.length) { index = skipSrcsetSeparators(value, index); const start = index; const candidate = getSrcsetCandidate(value, index); if (candidate.end > start) { candidates.push({ value: value.slice(start, candidate.end), offsets: [start, candidate.end], }); } index = candidate.hasTrailingComma ? candidate.next : getNextSrcsetCandidateStart(value, candidate.next); } return candidates; }