eslint-plugin-yaml
Version:
737 lines (736 loc) • 23.2 kB
JavaScript
;
const jsYaml = require("js-yaml");
const jshint = require("jshint");
const path = require("path");
const fs = require("fs");
const url = require("url");
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
function identifier(name) {
return {
type: "Identifier",
name
};
}
function literal(value) {
return {
type: "Literal",
value
};
}
function methodCall(object, name, args) {
return {
type: "CallExpression",
optional: false,
callee: {
type: "MemberExpression",
computed: false,
optional: false,
object,
property: identifier(name)
},
arguments: args
};
}
function processNumber(number) {
if (number < 0 || Object.is(number, -0)) {
return {
type: "UnaryExpression",
operator: "-",
prefix: true,
argument: processNumber(-number)
};
}
if (typeof number === "bigint") {
return {
type: "Literal",
bigint: String(number)
};
}
if (number === Number.POSITIVE_INFINITY || Number.isNaN(number)) {
return identifier(String(number));
}
return literal(number);
}
function processNumberArray(numbers) {
const elements = [];
for (const value of numbers) {
elements.push(processNumber(value));
}
return {
type: "ArrayExpression",
elements
};
}
function isStringReconstructable(value) {
return value instanceof URL || value instanceof URLSearchParams;
}
function isValueReconstructable(value) {
return value instanceof Boolean || value instanceof Date || value instanceof Number || value instanceof String;
}
const wellKnownSymbols = /* @__PURE__ */ new Map();
for (const name of Reflect.ownKeys(Symbol)) {
const value = Symbol[name];
if (typeof value === "symbol") {
wellKnownSymbols.set(value, name);
}
}
function isTypedArray(value) {
return value instanceof BigInt64Array || value instanceof BigUint64Array || value instanceof Float32Array || value instanceof Float64Array || value instanceof Int8Array || value instanceof Int16Array || value instanceof Int32Array || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Uint16Array || value instanceof Uint32Array;
}
function compareContexts(a, b) {
const aReferencedByB = a.referencedBy.has(b.value);
const bReferencedByA = b.referencedBy.has(a.value);
if (aReferencedByB) {
if (bReferencedByA) {
return a.count - b.count;
}
return -1;
}
if (bReferencedByA) {
return 1;
}
return a.count - b.count;
}
function replaceAssignment(expression, assignment) {
if (!assignment || assignment.type !== "AssignmentExpression") {
return expression;
}
let node = assignment;
while (node.right.type === "AssignmentExpression") {
node = node.right;
}
node.right = expression;
return assignment;
}
function symbolToEstree(symbol) {
const name = wellKnownSymbols.get(symbol);
if (name) {
return {
type: "MemberExpression",
computed: false,
optional: false,
object: identifier("Symbol"),
property: identifier(name)
};
}
if (symbol.description && symbol === Symbol.for(symbol.description)) {
return methodCall(identifier("Symbol"), "for", [literal(symbol.description)]);
}
throw new TypeError(`Only global symbols are supported, got: ${String(symbol)}`, {
cause: symbol
});
}
function property(key, value) {
const computed = typeof key !== "string";
return {
type: "Property",
method: false,
shorthand: false,
computed,
kind: "init",
key: computed ? symbolToEstree(key) : literal(key),
value
};
}
function temporalConstructor(name, values, calendar = "iso8601", defaultReferenceValue, referenceValue) {
if (calendar && typeof calendar !== "string") {
throw new Error(`Unsupported calendar: ${calendar}`, {
cause: calendar
});
}
const args = [];
if (referenceValue != null && (calendar !== "iso8601" || referenceValue !== defaultReferenceValue)) {
args.push(literal(referenceValue));
}
if (calendar !== "iso8601" || args.length !== 0) {
args.unshift(literal(calendar));
}
for (let index = values.length - 1; index >= 0; index -= 1) {
const value = values[index];
if (value !== 0 && value !== 0n || args.length !== 0) {
args.unshift(typeof value === "string" ? literal(value) : processNumber(value));
}
}
return {
type: "NewExpression",
callee: {
type: "MemberExpression",
computed: false,
optional: false,
object: identifier("Temporal"),
property: identifier(name)
},
arguments: args
};
}
function valueToEstree(value, options = {}) {
const stack = [];
const collectedContexts = /* @__PURE__ */ new Map();
const namedContexts = [];
function analyze(val) {
if (typeof val === "function") {
throw new TypeError(`Unsupported value: ${val}`, {
cause: val
});
}
if (typeof val !== "object") {
return;
}
if (val == null) {
return;
}
const context = collectedContexts.get(val);
if (context) {
if (options.preserveReferences) {
context.count += 1;
}
for (const ancestor of stack) {
context.referencedBy.add(ancestor);
}
if (stack.includes(val)) {
if (!options.preserveReferences) {
throw new Error(`Found circular reference: ${val}`, {
cause: val
});
}
const parent = stack.at(-1);
const parentContext = collectedContexts.get(parent);
parentContext.recursive = true;
context.recursive = true;
}
return;
}
collectedContexts.set(val, {
count: 1,
recursive: false,
referencedBy: new Set(stack),
value: val
});
if (isTypedArray(val)) {
return;
}
if (isStringReconstructable(val)) {
return;
}
if (isValueReconstructable(val)) {
return;
}
if (value instanceof RegExp) {
return;
}
if (typeof Temporal !== "undefined" && (value instanceof Temporal.Duration || value instanceof Temporal.Instant || value instanceof Temporal.PlainDate || value instanceof Temporal.PlainDateTime || value instanceof Temporal.PlainYearMonth || value instanceof Temporal.PlainMonthDay || value instanceof Temporal.PlainTime || value instanceof Temporal.ZonedDateTime)) {
return;
}
stack.push(val);
if (val instanceof Map) {
for (const pair of val) {
analyze(pair[0]);
analyze(pair[1]);
}
} else if (Array.isArray(val) || val instanceof Set) {
for (const entry of val) {
analyze(entry);
}
} else {
const proto = Object.getPrototypeOf(val);
if (proto != null && proto !== Object.prototype && !options.instanceAsObject) {
throw new TypeError(`Unsupported value: ${val}`, {
cause: val
});
}
for (const key of Reflect.ownKeys(val)) {
analyze(val[key]);
}
}
stack.pop();
}
function generate(val, isDeclaration) {
if (val === void 0) {
return identifier(String(val));
}
if (val == null || typeof val === "string" || typeof val === "boolean") {
return literal(val);
}
if (typeof val === "bigint" || typeof val === "number") {
return processNumber(val);
}
if (typeof val === "symbol") {
return symbolToEstree(val);
}
const context = collectedContexts.get(val);
if (!isDeclaration && (context == null ? void 0 : context.name)) {
return identifier(context.name);
}
if (isValueReconstructable(val)) {
return {
type: "NewExpression",
callee: identifier(val.constructor.name),
arguments: [generate(val.valueOf())]
};
}
if (val instanceof RegExp) {
return {
type: "Literal",
regex: {
pattern: val.source,
flags: val.flags
}
};
}
if (typeof Buffer !== "undefined" && Buffer.isBuffer(val)) {
return methodCall(identifier("Buffer"), "from", [processNumberArray(val)]);
}
if (isTypedArray(val)) {
return {
type: "NewExpression",
callee: identifier(val.constructor.name),
arguments: [processNumberArray(val)]
};
}
if (isStringReconstructable(val)) {
return {
type: "NewExpression",
callee: identifier(val.constructor.name),
arguments: [literal(String(val))]
};
}
if (typeof Temporal !== "undefined") {
if (val instanceof Temporal.Duration) {
return temporalConstructor("Duration", [val.years, val.months, val.weeks, val.days, val.hours, val.minutes, val.seconds, val.milliseconds, val.microseconds, val.nanoseconds]);
}
if (val instanceof Temporal.Instant) {
return temporalConstructor("Instant", [val.epochNanoseconds]);
}
if (val instanceof Temporal.PlainDate) {
const iso = val.getISOFields();
return temporalConstructor("PlainDate", [iso.isoYear, iso.isoMonth, iso.isoDay], iso.calendar);
}
if (val instanceof Temporal.PlainDateTime) {
const iso = val.getISOFields();
return temporalConstructor("PlainDateTime", [iso.isoYear, iso.isoMonth, iso.isoDay, iso.isoHour, iso.isoMinute, iso.isoSecond, iso.isoMillisecond, iso.isoMicrosecond, iso.isoNanosecond], iso.calendar);
}
if (val instanceof Temporal.PlainMonthDay) {
const iso = val.getISOFields();
return temporalConstructor("PlainMonthDay", [iso.isoMonth, iso.isoDay], iso.calendar, 1972, iso.isoYear);
}
if (val instanceof Temporal.PlainTime) {
const iso = val.getISOFields();
return temporalConstructor("PlainTime", [iso.isoHour, iso.isoMinute, iso.isoSecond, iso.isoMillisecond, iso.isoMicrosecond, iso.isoNanosecond]);
}
if (val instanceof Temporal.PlainYearMonth) {
const iso = val.getISOFields();
return temporalConstructor("PlainYearMonth", [iso.isoYear, iso.isoMonth], iso.calendar, 1, iso.isoDay);
}
if (val instanceof Temporal.ZonedDateTime) {
const iso = val.getISOFields();
return temporalConstructor("ZonedDateTime", [val.epochNanoseconds, val.timeZoneId], iso.calendar);
}
}
if (Array.isArray(val)) {
const elements = Array.from({
length: val.length
});
let trimmable;
for (let index = 0; index < val.length; index += 1) {
if (!(index in val)) {
elements[index] = null;
trimmable = void 0;
continue;
}
const child = val[index];
const childContext = collectedContexts.get(child);
if (context && childContext && namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) {
elements[index] = null;
trimmable || (trimmable = index);
childContext.assignment = {
type: "AssignmentExpression",
operator: "=",
left: {
type: "MemberExpression",
computed: true,
optional: false,
object: identifier(context.name),
property: literal(index)
},
right: childContext.assignment || identifier(childContext.name)
};
} else {
elements[index] = generate(child);
trimmable = void 0;
}
}
if (trimmable != null) {
elements.splice(trimmable);
}
return {
type: "ArrayExpression",
elements
};
}
if (val instanceof Set) {
const elements = [];
let finalizer;
for (const child of val) {
if (finalizer) {
finalizer = methodCall(finalizer, "add", [generate(child)]);
} else {
const childContext = collectedContexts.get(child);
if (context && childContext && namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) {
finalizer = methodCall(identifier(context.name), "add", [generate(child)]);
} else {
elements.push(generate(child));
}
}
}
if (context && finalizer) {
context.assignment = replaceAssignment(finalizer, context.assignment);
}
return {
type: "NewExpression",
callee: identifier("Set"),
arguments: elements.length ? [{
type: "ArrayExpression",
elements
}] : []
};
}
if (val instanceof Map) {
const elements = [];
let finalizer;
for (const [key, item] of val) {
if (finalizer) {
finalizer = methodCall(finalizer, "set", [generate(key), generate(item)]);
} else {
const keyContext = collectedContexts.get(key);
const itemContext = collectedContexts.get(item);
if (context && (keyContext && namedContexts.indexOf(keyContext) >= namedContexts.indexOf(context) || itemContext && namedContexts.indexOf(itemContext) >= namedContexts.indexOf(context))) {
finalizer = methodCall(identifier(context.name), "set", [generate(key), generate(item)]);
} else {
elements.push({
type: "ArrayExpression",
elements: [generate(key), generate(item)]
});
}
}
}
if (context && finalizer) {
context.assignment = replaceAssignment(finalizer, context.assignment);
}
return {
type: "NewExpression",
callee: identifier("Map"),
arguments: elements.length ? [{
type: "ArrayExpression",
elements
}] : []
};
}
const properties = [];
if (Object.getPrototypeOf(val) == null) {
properties.push(property("__proto__", literal(null)));
}
const object = val;
const propertyDescriptors = [];
for (const key of Reflect.ownKeys(val)) {
const child = object[key];
const {
configurable,
enumerable,
writable
} = Object.getOwnPropertyDescriptor(val, key);
const childContext = collectedContexts.get(child);
if (!configurable || !enumerable || !writable) {
const propertyDescriptor = [property("value", generate(child))];
if (configurable) {
propertyDescriptor.push(property("configurable", literal(true)));
}
if (enumerable) {
propertyDescriptor.push(property("enumerable", literal(true)));
}
if (writable) {
propertyDescriptor.push(property("writable", literal(true)));
}
propertyDescriptors.push(property(key, {
type: "ObjectExpression",
properties: propertyDescriptor
}));
} else if (context && childContext && namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) {
childContext.assignment = {
type: "AssignmentExpression",
operator: "=",
left: {
type: "MemberExpression",
computed: true,
optional: false,
object: identifier(context.name),
property: generate(key)
},
right: childContext.assignment || generate(child)
};
} else {
properties.push(property(key, generate(child)));
}
}
const objectExpression = {
type: "ObjectExpression",
properties
};
if (propertyDescriptors.length) {
if (!context) {
return methodCall(identifier("Object"), "defineProperties", [objectExpression, {
type: "ObjectExpression",
properties: propertyDescriptors
}]);
}
context.assignment = replaceAssignment(methodCall(identifier("Object"), "defineProperties", [identifier(context.name), {
type: "ObjectExpression",
properties: propertyDescriptors
}]), context.assignment);
}
return objectExpression;
}
analyze(value);
for (const [val, context] of collectedContexts) {
if (context.recursive || context.count > 1) {
context.name = `$${namedContexts.length}`;
namedContexts.push(context);
} else {
collectedContexts.delete(val);
}
}
if (!namedContexts.length) {
return generate(value);
}
const params = namedContexts.sort(compareContexts).map((context) => ({
type: "AssignmentPattern",
left: identifier(context.name),
right: generate(context.value, true)
}));
const rootContext = collectedContexts.get(value);
const finalizers = [];
for (const context of collectedContexts.values()) {
if (context !== rootContext && context.assignment) {
finalizers.push(context.assignment);
}
}
finalizers.push(rootContext ? rootContext.assignment || identifier(rootContext.name) : generate(value));
return {
type: "CallExpression",
optional: false,
arguments: [],
callee: {
type: "ArrowFunctionExpression",
expression: false,
params,
body: {
type: "SequenceExpression",
expressions: finalizers
}
}
};
}
class YamlProcessor {
/** Map of file paths to their YAML value and warnings. */
parsedFiles = /* @__PURE__ */ new Map();
// TODO: Implement autofix
supportsAutofix = false;
/** The preprocess method is called by ESLint before the parser is run. */
preprocess(text, path2) {
return [{ text, filename: path2 }];
}
/** Parser for YAML files. */
parseForESLint(code, options) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
const ast = {
type: "Program",
body: [],
sourceType: "script",
tokens: [],
comments: [],
range: [0, code.length],
loc: {
start: { line: 1, column: 0 },
end: {
line: (_b = (_a = code.split("\n")) == null ? void 0 : _a.length) != null ? _b : 1,
column: (_f = (_e = (_d = (_c = code.split("\n")) == null ? void 0 : _c.slice(-1)) == null ? void 0 : _d[0]) == null ? void 0 : _e.length) != null ? _f : 0
}
}
};
try {
const { yamlDocs, warnings } = this.loadYaml(code, options == null ? void 0 : options.filePath);
const statement = {
type: "ExpressionStatement",
expression: valueToEstree(yamlDocs)
};
ast.body.push(statement);
this.parsedFiles.set(code, {
value: yamlDocs,
messages: warnings.map((warning) => {
var _a2, _b2, _c2, _d2, _e2;
return {
ruleId: "yaml-warning",
severity: 2,
message: warning.message,
source: (_a2 = warning.mark) == null ? void 0 : _a2.buffer,
line: (_c2 = (_b2 = warning.mark) == null ? void 0 : _b2.line) != null ? _c2 : 0,
column: (_e2 = (_d2 = warning.mark) == null ? void 0 : _d2.column) != null ? _e2 : 0
};
})
});
} catch (err) {
const { message, mark } = err;
const key = (_g = options == null ? void 0 : options.filePath) != null ? _g : code;
this.parsedFiles.set(key, {
value: [],
messages: [
{
ruleId: "invalid-yaml",
severity: 2,
message,
line: (_h = mark == null ? void 0 : mark.line) != null ? _h : 0,
column: (_i = mark == null ? void 0 : mark.column) != null ? _i : 0
}
]
});
}
return { ast };
}
/** The postprocess method is called by ESLint after the parser is run. */
postprocess(_messages, filePath) {
if (!this.isYaml(filePath)) {
return [];
}
let value = this.parsedFiles.get(filePath);
if (value === void 0) {
const key = Array.from(this.parsedFiles.keys()).find((key2) => key2.endsWith(filePath));
if (key === void 0) {
return [];
}
value = this.parsedFiles.get(key);
}
if (value.messages.length > 0) {
this.parsedFiles.delete(filePath);
return value.messages;
}
const errors = value.value.flatMap((yamlDoc) => this.lintJSON(yamlDoc));
const linter_messages = errors.map((error) => {
const { reason, evidence, line, character } = error;
return {
ruleId: "bad-yaml",
severity: 2,
message: reason,
source: evidence,
line,
column: character
};
});
this.parsedFiles.delete(filePath);
return linter_messages;
}
isYaml(filePath) {
const fileExtension = path.extname(filePath);
return [".yaml", ".yml"].includes(fileExtension);
}
loadYaml(fileContent, filePath) {
const warnings = [];
const yamlDocs = jsYaml.loadAll(fileContent, void 0, {
filename: filePath,
json: false,
onWarning: (exception) => {
warnings.push(exception);
}
});
return { yamlDocs, warnings };
}
lintJSON(yamlDoc) {
var _a;
const yaml_json = JSON.stringify(yamlDoc, null, 2);
jshint.JSHINT(yaml_json);
const data = jshint.JSHINT.data();
const errors = (_a = data == null ? void 0 : data.errors) != null ? _a : [];
return errors;
}
}
function getPackageJson() {
try {
const dirname = typeof __dirname === "string" ? __dirname : path.dirname(url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href));
const pkgPath = path.join(path.dirname(dirname), "package.json");
return JSON.parse(fs.readFileSync(pkgPath, "utf8"));
} catch (err) {
console.error(err);
return {
name: "eslint-plugin-yaml",
version: "1.0.3"
};
}
}
const pkg = getPackageJson();
const yamlProcessor = new YamlProcessor();
const parser = {
meta: {
name: "yaml-eslint-parser",
version: pkg.version
},
parseForESLint: yamlProcessor.parseForESLint.bind(yamlProcessor)
};
const processors = {
[pkg.name]: {
meta: {
name: pkg.name,
version: pkg.version
},
postprocess: yamlProcessor.postprocess.bind(yamlProcessor)
}
};
const meta = {
name: pkg.name,
version: pkg.version,
type: "problem",
docs: {
description: "YAML linting",
category: "Parsing Issues",
recommended: false,
url: "https://github.com/aminya/eslint-plugin-yaml"
},
fixable: "code",
schema: []
};
const plugin = {
meta,
processors,
configs: {
recommended: {},
legacy: {}
}
};
const files = ["**/*.yml", "**/*.yaml", "!**/node_modules/**", "!**/pnpm-lock.yaml", "**/.github/**.{yml,yaml}"];
const recommendedConfig = {
name: `${pkg.name}/recommended}`,
files,
processor: {
name: pkg.name,
preprocess: yamlProcessor.preprocess.bind(yamlProcessor),
postprocess: yamlProcessor.postprocess.bind(yamlProcessor)
},
plugins: {
[pkg.name]: plugin
},
languageOptions: {
parser
}
};
plugin.configs.recommended = recommendedConfig;
const legacyConfig = {
overrides: [
{
plugins: [pkg.name],
files,
processor: `yaml/${pkg.name}`
}
]
};
plugin.configs.legacy = legacyConfig;
module.exports = plugin;
module.exports.default = plugin;
module.exports = plugin;
//# sourceMappingURL=index.cjs.map