UNPKG

eslint-plugin-yaml

Version:
737 lines (736 loc) 23.2 kB
"use strict"; 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