| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 | 'use strict';const {upperFirst} = require('lodash');const MESSAGE_ID_INVALID_EXPORT = 'invalidExport';const messages = {	[MESSAGE_ID_INVALID_EXPORT]: 'Exported error name should match error class',};const nameRegexp = /^(?:[A-Z][\da-z]*)*Error$/;const getClassName = name => upperFirst(name).replace(/(?:error|)$/i, 'Error');const getConstructorMethod = className => `	constructor() {		super();		this.name = '${className}';	}`;const hasValidSuperClass = node => {	if (!node.superClass) {		return false;	}	let {name, type, property} = node.superClass;	if (type === 'MemberExpression') {		({name} = property);	}	return nameRegexp.test(name);};const isSuperExpression = node =>	node.type === 'ExpressionStatement'	&& node.expression.type === 'CallExpression'	&& node.expression.callee.type === 'Super';const isAssignmentExpression = (node, name) => {	if (		node.type !== 'ExpressionStatement'		|| node.expression.type !== 'AssignmentExpression'	) {		return false;	}	const lhs = node.expression.left;	if (!lhs.object || lhs.object.type !== 'ThisExpression') {		return false;	}	return lhs.property.name === name;};const isPropertyDefinition = (node, name) =>	node.type === 'PropertyDefinition'	&& !node.computed	&& node.key.type === 'Identifier'	&& node.key.name === name;function * customErrorDefinition(context, node) {	if (!hasValidSuperClass(node)) {		return;	}	if (node.id === null) {		return;	}	const {name} = node.id;	const className = getClassName(name);	if (name !== className) {		yield {			node: node.id,			message: `Invalid class name, use \`${className}\`.`,		};	}	const {body, range} = node.body;	const constructor = body.find(x => x.kind === 'constructor');	if (!constructor) {		yield {			node,			message: 'Add a constructor to your error.',			fix: fixer => fixer.insertTextAfterRange([				range[0],				range[0] + 1,			], getConstructorMethod(name)),		};		return;	}	const constructorBodyNode = constructor.value.body;	// Verify the constructor has a body (TypeScript)	if (!constructorBodyNode) {		return;	}	const constructorBody = constructorBodyNode.body;	const superExpression = constructorBody.find(body => isSuperExpression(body));	const messageExpressionIndex = constructorBody.findIndex(x => isAssignmentExpression(x, 'message'));	if (!superExpression) {		yield {			node: constructorBodyNode,			message: 'Missing call to `super()` in constructor.',		};	} else if (messageExpressionIndex !== -1) {		const expression = constructorBody[messageExpressionIndex];		yield {			node: superExpression,			message: 'Pass the error message to `super()` instead of setting `this.message`.',			* fix(fixer) {				if (superExpression.expression.arguments.length === 0) {					const rhs = expression.expression.right;					yield fixer.insertTextAfterRange([						superExpression.range[0],						superExpression.range[0] + 6,					], rhs.raw || rhs.name);				}				yield fixer.removeRange([					messageExpressionIndex === 0 ? constructorBodyNode.range[0] : constructorBody[messageExpressionIndex - 1].range[1],					expression.range[1],				]);			},		};	}	const nameExpression = constructorBody.find(x => isAssignmentExpression(x, 'name'));	if (!nameExpression) {		const nameProperty = body.find(node => isPropertyDefinition(node, 'name'));		if (!nameProperty?.value || nameProperty.value.value !== name) {			yield {				node: nameProperty?.value ?? constructorBodyNode,				message: `The \`name\` property should be set to \`${name}\`.`,			};		}	} else if (nameExpression.expression.right.value !== name) {		yield {			node: nameExpression?.expression.right ?? constructorBodyNode,			message: `The \`name\` property should be set to \`${name}\`.`,		};	}}const customErrorExport = (context, node) => {	const exportsName = node.left.property.name;	const maybeError = node.right;	if (maybeError.type !== 'ClassExpression') {		return;	}	if (!hasValidSuperClass(maybeError)) {		return;	}	if (!maybeError.id) {		return;	}	// Assume rule has already fixed the error name	const errorName = maybeError.id.name;	if (exportsName === errorName) {		return;	}	return {		node: node.left.property,		messageId: MESSAGE_ID_INVALID_EXPORT,		fix: fixer => fixer.replaceText(node.left.property, errorName),	};};/** @param {import('eslint').Rule.RuleContext} context */const create = context => ({	ClassDeclaration: node => customErrorDefinition(context, node),	'AssignmentExpression[right.type="ClassExpression"]': node => customErrorDefinition(context, node.right),	'AssignmentExpression[left.type="MemberExpression"][left.object.type="Identifier"][left.object.name="exports"]': node => customErrorExport(context, node),});/** @type {import('eslint').Rule.RuleModule} */module.exports = {	create,	meta: {		type: 'problem',		docs: {			description: 'Enforce correct `Error` subclassing.',		},		fixable: 'code',		messages,	},};
 |