eslint-plugin-yml
Version:
This ESLint plugin provides linting rules for YAML.
271 lines (270 loc) • 9.94 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("../utils/index");
const ast_utils_1 = require("../utils/ast-utils");
const yaml_1 = require("../utils/yaml");
const compat_1 = require("../utils/compat");
const OPTIONS_ENUM = ["always", "never", "ignore"];
function parseOptions(option) {
const opt = {
singleline: "ignore",
multiline: "always",
};
if (option) {
if (typeof option === "string") {
opt.singleline = option;
opt.multiline = option;
}
else {
if (typeof option.singleline === "string") {
opt.singleline = option.singleline;
}
if (typeof option.multiline === "string") {
opt.multiline = option.multiline;
}
}
}
return opt;
}
exports.default = (0, index_1.createRule)("block-mapping", {
meta: {
docs: {
description: "require or disallow block style mappings.",
categories: ["standard"],
extensionRule: false,
layout: false,
},
fixable: "code",
schema: [
{
anyOf: [
{ enum: ["always", "never"] },
{
type: "object",
properties: {
singleline: { enum: OPTIONS_ENUM },
multiline: { enum: OPTIONS_ENUM },
},
additionalProperties: false,
},
],
},
],
messages: {
required: "Must use block style mappings.",
disallow: "Must use flow style mappings.",
},
type: "layout",
},
create(context) {
var _a;
const sourceCode = (0, compat_1.getSourceCode)(context);
if (!((_a = sourceCode.parserServices) === null || _a === void 0 ? void 0 : _a.isYAML)) {
return {};
}
const options = parseOptions(context.options[0]);
let styleStack = null;
function downStack(node) {
if (styleStack) {
if (node.style === "flow") {
styleStack.hasFlowStyle = true;
}
else if (node.style === "block") {
styleStack.hasBlockStyle = true;
}
}
styleStack = {
upper: styleStack,
node,
flowStyle: node.style === "flow",
blockStyle: node.style === "block",
withinFlowStyle: (styleStack &&
(styleStack.withinFlowStyle || styleStack.flowStyle)) ||
false,
withinBlockStyle: (styleStack &&
(styleStack.withinBlockStyle || styleStack.blockStyle)) ||
false,
};
}
function upStack() {
if (styleStack && styleStack.upper) {
styleStack.upper.hasNullPair =
styleStack.upper.hasNullPair || styleStack.hasNullPair;
styleStack.upper.hasBlockLiteralOrFolded =
styleStack.upper.hasBlockLiteralOrFolded ||
styleStack.hasBlockLiteralOrFolded;
styleStack.upper.hasBlockStyle =
styleStack.upper.hasBlockStyle || styleStack.hasBlockStyle;
styleStack.upper.hasFlowStyle =
styleStack.upper.hasFlowStyle || styleStack.hasFlowStyle;
}
styleStack = styleStack && styleStack.upper;
}
return {
YAMLSequence: downStack,
YAMLMapping: downStack,
YAMLPair(node) {
if (node.key == null || node.value == null) {
styleStack.hasNullPair = true;
}
},
YAMLScalar(node) {
if (styleStack &&
(node.style === "folded" || node.style === "literal")) {
styleStack.hasBlockLiteralOrFolded = true;
}
},
"YAMLSequence:exit": upStack,
"YAMLMapping:exit"(node) {
const mappingInfo = styleStack;
upStack();
if (node.pairs.length === 0) {
return;
}
const multiline = node.loc.start.line < node.loc.end.line;
const optionType = multiline ? options.multiline : options.singleline;
if (optionType === "ignore") {
return;
}
if (node.style === "flow") {
if (optionType === "never") {
return;
}
if ((0, yaml_1.isKeyNode)(node)) {
return;
}
const canFix = canFixToBlock(mappingInfo, node) && !(0, yaml_1.hasTabIndent)(context);
context.report({
loc: node.loc,
messageId: "required",
fix: (canFix && buildFixFlowToBlock(node, context)) || null,
});
}
else if (node.style === "block") {
if (optionType === "always") {
return;
}
const canFix = canFixToFlow(mappingInfo, node) && !(0, yaml_1.hasTabIndent)(context);
context.report({
loc: node.loc,
messageId: "disallow",
fix: (canFix && buildFixBlockToFlow(node, context)) || null,
});
}
},
};
},
});
function canFixToBlock(mappingInfo, node) {
if (mappingInfo.hasNullPair || mappingInfo.hasBlockLiteralOrFolded) {
return false;
}
if (mappingInfo.withinFlowStyle) {
return false;
}
for (const pair of node.pairs) {
const key = pair.key;
if (key.loc.start.line < key.loc.end.line) {
return false;
}
}
return true;
}
function canFixToFlow(mappingInfo, node) {
if (mappingInfo.hasNullPair || mappingInfo.hasBlockLiteralOrFolded) {
return false;
}
if (mappingInfo.hasBlockStyle) {
return false;
}
for (const pair of node.pairs) {
const value = (0, yaml_1.unwrapMeta)(pair.value);
const key = (0, yaml_1.unwrapMeta)(pair.key);
if (value && value.type === "YAMLScalar" && value.style === "plain") {
if (value.loc.start.line < value.loc.end.line) {
return false;
}
if (/[[\]{}]/u.test(value.strValue)) {
return false;
}
if (value.strValue.includes(",")) {
return false;
}
}
if (key && key.type === "YAMLScalar" && key.style === "plain") {
if (/[[\]{]/u.test(key.strValue)) {
return false;
}
if (/[,}]/u.test(key.strValue)) {
return false;
}
}
}
return true;
}
function buildFixFlowToBlock(node, context) {
return function* (fixer) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const open = sourceCode.getFirstToken(node);
const close = sourceCode.getLastToken(node);
if ((open === null || open === void 0 ? void 0 : open.value) !== "{" || (close === null || close === void 0 ? void 0 : close.value) !== "}") {
return;
}
const expectIndent = (0, yaml_1.calcExpectIndentForPairs)(node, context);
if (expectIndent == null) {
return;
}
const openPrevToken = sourceCode.getTokenBefore(open, {
includeComments: true,
});
if (!openPrevToken) {
yield fixer.removeRange([sourceCode.ast.range[0], open.range[1]]);
}
else if (openPrevToken.loc.end.line < open.loc.start.line) {
yield fixer.removeRange([openPrevToken.range[1], open.range[1]]);
}
else {
yield fixer.remove(open);
}
let prev = open;
for (const pair of node.pairs) {
const prevToken = sourceCode.getTokenBefore(pair, {
includeComments: true,
filter: (token) => !(0, ast_utils_1.isComma)(token),
});
yield* removeComma(prev, prevToken);
yield fixer.replaceTextRange([prevToken.range[1], pair.range[0]], `\n${expectIndent}`);
const colonToken = sourceCode.getTokenAfter(pair.key, ast_utils_1.isColon);
if (colonToken.range[1] ===
sourceCode.getTokenAfter(colonToken, {
includeComments: true,
}).range[0]) {
yield fixer.insertTextAfter(colonToken, " ");
}
yield* (0, yaml_1.processIndentFix)(fixer, expectIndent, pair.value, context);
prev = pair;
}
yield* removeComma(prev, close);
yield fixer.remove(close);
function* removeComma(a, b) {
for (const token of sourceCode.getTokensBetween(a, b, {
includeComments: true,
})) {
if ((0, ast_utils_1.isComma)(token)) {
yield fixer.remove(token);
}
}
}
};
}
function buildFixBlockToFlow(node, _context) {
return function* (fixer) {
yield fixer.insertTextBefore(node, "{");
const pairs = [...node.pairs];
const lastPair = pairs.pop();
for (const pair of pairs) {
yield fixer.insertTextAfter(pair, ",");
}
yield fixer.insertTextAfter(lastPair || node, "}");
};
}