ecmarkup
Version:
Custom element definitions and core utilities for markup that specifies ECMAScript and related technologies.
113 lines (112 loc) • 4.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = default_1;
const expr_parser_1 = require("../../expr-parser");
const utils_1 = require("../../utils");
const ruleId = 'algorithm-spelling';
// Note that these will be composed, so cannot contain backreferences
const matchers = [
{
pattern: /\b[Ii]ncrement\b/gu,
message: 'prefer "set _x_ to _x_ + 1" over "increment"',
},
{
pattern: /\b[Dd]ecrement\b/gu,
message: 'prefer "set _x_ to _x_ - 1" over "decrement"',
},
{
pattern: /\b[Ii]ncrease _/gu,
message: 'prefer "set _x_ to _x_ + _y_" over "increase _x_"',
},
{
pattern: /\b[Dd]ecrease _/gu,
message: 'prefer "set _x_ to _x_ - _y_" over "decrease _x_"',
},
{
pattern: /\bis an element of\b/gu,
message: 'prefer "_list_ contains _element_" over "_element_ is an element of _list_"',
},
{
pattern: /(?<=\.\[\[\w+\]\] )is present\b/gu,
message: 'prefer "_record_ has a [[Field]] field" over "_record_.[[Field]] is present"',
},
{
pattern: /(?<=\.\[\[Type\]\] )is ~(?:throw|normal|return|continue|break)~/gu,
message: 'prefer "is a throw completion", "is a normal completion", etc. over accessing [[Type]]',
},
{
pattern: /\bis either null or undefined\b/gu,
message: 'prefer "is either undefined or null" for consistency',
},
{
pattern: /\bis that of\b/gu,
message: 'prefer "whose _ is _" over "whose _ is that of _"',
},
{
pattern: /\bare both\b/gu,
message: 'prefer "if _a_ is _c_ and _b_ is _c_" over "if _a_ and _b_ are both _c_"',
},
{
pattern: /\b(?:a|an) (?:String|Number|Boolean|BigInt|Symbol|Object|List|Record|Set|Relation|Enum|Property Descriptor|(?:Reference|Completion|Environment|ClassFieldDefinition|ClassStaticBlockDefinition) Record|Abstract Closure|PrivateElement) value\b/gu,
message: 'do not say "value" after the name of a spec type when following a determiner: prefer "a String" over "a String value"',
},
];
const composed = new RegExp(matchers.map(m => `(?:${m.pattern.source})`).join('|'), 'u');
function default_1(report, step, algorithmSource, parsedSteps) {
if (step.contents.length === 0) {
return;
}
const stepSource = algorithmSource.slice(step.contents[0].location.start.offset, step.contents[step.contents.length - 1].location.end.offset);
if (/^(?:NOTE:|Assert:)/.test(stepSource)) {
return;
}
const baseOffset = step.contents[0].location.start.offset;
// structural checks using the parsed expression tree
const stepSeq = parsedSteps.get(step);
if (stepSeq != null) {
(0, expr_parser_1.walk)((expr, path) => {
// check *""* outside of AO argument lists
if (expr.name === 'star' &&
expr.contents.length === 1 &&
expr.contents[0].name === 'text' &&
expr.contents[0].contents === '""') {
const inArgList = path.some(p => p.parent.name === 'call' || p.parent.name === 'sdo-call');
if (!inArgList) {
report({
ruleId: 'prefer-empty-string',
message: 'prefer "the empty String" over *""*',
...(0, utils_1.offsetToLineAndColumn)(algorithmSource, expr.location.start.offset),
});
}
}
}, stepSeq);
}
// check "or if" / "and if" — but only when the other connective is not also present
const orIfPattern = /(?<=\bIf .+) (or|and) if\b/gu;
let orIfMatch = orIfPattern.exec(stepSource);
while (orIfMatch !== null) {
const otherPattern = orIfMatch[1] === 'or' ? /\band\b/ : /\bor\b/;
if (!otherPattern.test(stepSource)) {
report({
ruleId,
message: `prefer "if _a_ ${orIfMatch[1]} _b_" over "if _a_ ${orIfMatch[1]} if _b_"`,
...(0, utils_1.offsetToLineAndColumn)(algorithmSource, baseOffset + orIfMatch.index),
});
}
orIfMatch = orIfPattern.exec(stepSource);
}
if (!composed.test(stepSource)) {
return;
}
for (const { pattern, message } of matchers) {
let match = pattern.exec(stepSource);
while (match !== null) {
report({
ruleId,
message,
...(0, utils_1.offsetToLineAndColumn)(algorithmSource, baseOffset + match.index),
});
match = pattern.exec(stepSource);
}
}
}