UNPKG

ecmarkup

Version:

Custom element definitions and core utilities for markup that specifies ECMAScript and related technologies.

330 lines (328 loc) 14.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ruleId = 'algorithm-line-style'; /* Checks that every algorithm step has one of these forms: - `If foo, bar.` - `If foo, then` + substeps - `If foo, bar, or baz; then` + substeps - `Else if foo, bar.` - `Else if foo, then` + substeps - `Else if foo, bar, or baz; then` + substeps - `Else, baz.` - `Else,` + substeps - `Repeat,` + substeps - `Repeat, while foo,` + substeps - `Repeat, until foo,` + substeps - `For each foo, bar.` - `For each foo, do` + substeps - `NOTE: Something.` - `Assert: Something.` - `Other.` - `Other:` + substeps */ function default_1(report, node, algorithmSource) { if (node.hasAttribute('example')) { return {}; } return { enter(node) { if (node.name !== 'ordered-list-item') { return; } let firstIndex = 0; let lastIndex = node.contents.length - 1; // Special case: ignore <ins>, <del>, or <mark> tags that surround a whole // line, and lint the enclosed line as if there were no tag. let first = node.contents[firstIndex]; let last = node.contents[lastIndex]; while (first.name === 'tag' && last.name === 'tag' && ((first.contents === '<mark>' && last.contents === '</mark>') || (first.contents === '<ins>' && last.contents === '</ins>') || (first.contents === '<del>' && last.contents === '</del>'))) { ++firstIndex; --lastIndex; first = node.contents[firstIndex]; last = node.contents[lastIndex]; } while (firstIndex <= lastIndex && node.contents[firstIndex].name === 'tag') { ++firstIndex; } if (firstIndex > lastIndex) { report({ ruleId, line: node.location.start.line, column: node.location.start.column, message: 'expected line to contain non-tag elements', }); return; } first = node.contents[firstIndex]; last = node.contents[lastIndex]; // Special case: if the step has a figure, it should end in `:` if (last.name === 'tag' && last.contents === '</figure>') { let count = 1; --lastIndex; if (lastIndex < 0) { report({ ruleId, line: node.contents[0].location.start.line, column: node.contents[0].location.start.column, message: 'could not find matching <figure> tag', }); return; } while (count > 0) { last = node.contents[lastIndex]; if (last.name === 'tag') { if (last.contents === '<figure>') { --count; } else if (last.contents === '</figure>') { ++count; } } --lastIndex; if (lastIndex < 0) { report({ ruleId, line: node.contents[0].location.start.line, column: node.contents[0].location.start.column, message: 'could not find matching <figure> tag', }); return; } } last = node.contents[lastIndex]; if (last.name !== 'text') { report({ ruleId, line: last.location.start.line, column: last.location.start.column, message: `expected line to end with text (found ${last.name})`, }); return; } if (!/:\n +$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: 'expected line with figure to end with ":"', }); } return; } const hasSubsteps = node.sublist !== null; // Special case: only lines without substeps can end in `pre` tags. if (last.name === 'opaqueTag' && /^\s*<pre>/.test(last.contents)) { if (hasSubsteps) { report({ ruleId, line: node.contents[0].location.start.line, column: node.contents[0].location.start.column, message: `lines ending in <pre> tags must not have substeps`, }); } return; } if (last.name !== 'text') { report({ ruleId, line: last.location.start.line, column: last.location.start.column, message: `expected line to end with text (found ${last.name})`, }); return; } const initialText = first.name === 'text' ? first.contents : ''; if (/^(?:If |Else if)/.test(initialText)) { if (hasSubsteps) { if (node.sublist.name === 'ol') { const end = last.contents.match(/[,;] then$/); if (!end) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected "If" with substeps to end with ", then" (found ${JSON.stringify(last.contents)})`, }); } else if (end[0][0] === ';' && !node.contents.some(c => c.name === 'text' && /,/.test(c.contents))) { report({ ruleId, line: last.location.end.line, column: last.location.end.column - 6, message: `expected "If" with substeps to end with ", then" rather than "; then" when there are no other commas`, }); } } else { if (!/:$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected "If" with list to end with ":" (found ${JSON.stringify(last.contents)})`, }); } } } else { const lineSource = algorithmSource.slice(first.location.start.offset, last.location.end.offset); const ifThenMatch = lineSource.match(/^If[^,\n]+, then /); if (ifThenMatch != null) { report({ ruleId, line: first.location.start.line, column: first.location.start.column + ifThenMatch[0].length - 5, message: `single-line "If" steps should not have a "then"`, }); } if (!/(?:\.|\.\)|:)$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected "If" without substeps to end with "." or ":" (found ${JSON.stringify(last.contents)})`, }); } } } else if (/^Else/.test(initialText)) { if (/^Else, if/.test(initialText)) { report({ ruleId, line: first.location.start.line, column: first.location.start.column + 4, message: `prefer "Else if" over "Else, if"`, }); } if (hasSubsteps) { if (node.contents.length === 1 && first.contents === 'Else,') { return; } if (!/,$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected "Else" with substeps to end with "," (found ${JSON.stringify(last.contents)})`, }); } } else { if (!/(?:\.|\.\)|:)$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected "Else" without substeps to end with "." or ":" (found ${JSON.stringify(last.contents)})`, }); } } } else if (/^Repeat/.test(initialText)) { if (!hasSubsteps) { report({ ruleId, line: node.contents[0].location.start.line, column: node.contents[0].location.start.column, message: 'expected "Repeat" to have substeps', }); } if (node.contents.length === 1 && first.contents === 'Repeat,') { return; } if (!/^Repeat, (?:while|until) /.test(initialText)) { report({ ruleId, line: node.contents[0].location.start.line, column: node.contents[0].location.start.column, message: `expected "Repeat" to start with "Repeat, while " or "Repeat, until " (found ${JSON.stringify(initialText)})`, }); } if (!/,$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: 'expected "Repeat" to end with ","', }); } } else if (/^For each/.test(initialText)) { if (hasSubsteps) { if (!/, do$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected "For each" with substeps to end with ", do" (found ${JSON.stringify(last.contents)})`, }); } } else { if (!/(?:\.|\.\))$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected "For each" without substeps to end with "." (found ${JSON.stringify(last.contents)})`, }); } } } else { // these are not else-ifs because we still want to enforce the line-ending rules if (/^(NOTE|Assert): [a-z]/.test(initialText)) { const kind = initialText.match(/^(NOTE|Assert)/)[1]; report({ ruleId, line: first.location.start.line, column: first.location.start.column + kind.length + 2, message: `the clause after "${kind}:" should begin with a capital letter`, }); } if (/^NOTE:/i.test(initialText) && !/^NOTE:/.test(initialText)) { report({ ruleId, line: first.location.start.line, column: first.location.start.column, message: `"NOTE:" should be fully capitalized`, }); } if (/^Assert:/i.test(initialText) && !/^Assert:/.test(initialText)) { report({ ruleId, line: first.location.start.line, column: first.location.start.column, message: `"Assert:" should be capitalized`, }); } if (hasSubsteps) { if (!/:$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected freeform line with substeps to end with ":" (found ${JSON.stringify(last.contents)})`, }); } } else if (!/(?:\.|\.\))$/.test(last.contents)) { report({ ruleId, line: last.location.end.line, column: last.location.end.column, message: `expected freeform line to end with "." (found ${JSON.stringify(last.contents)})`, }); } } return; }, }; } exports.default = default_1;