| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 | 'use strict';const {getStaticValue} = require('@eslint-community/eslint-utils');const {newExpressionSelector} = require('./selectors/index.js');const {replaceStringLiteral} = require('./fix/index.js');const MESSAGE_ID_NEVER = 'never';const MESSAGE_ID_ALWAYS = 'always';const MESSAGE_ID_REMOVE = 'remove';const messages = {	[MESSAGE_ID_NEVER]: 'Remove the `./` prefix from the relative URL.',	[MESSAGE_ID_ALWAYS]: 'Add a `./` prefix to the relative URL.',	[MESSAGE_ID_REMOVE]: 'Remove leading `./`.',};const templateLiteralSelector = [	newExpressionSelector({name: 'URL', argumentsLength: 2}),	' > TemplateLiteral.arguments:first-child',].join('');const literalSelector = [	newExpressionSelector({name: 'URL', argumentsLength: 2}),	' > Literal.arguments:first-child',].join('');const DOT_SLASH = './';const TEST_URL_BASES = [	'https://example.com/a/b/',	'https://example.com/a/b.html',];const isSafeToAddDotSlashToUrl = (url, base) => {	try {		return new URL(url, base).href === new URL(DOT_SLASH + url, base).href;	} catch {}	return false;};const isSafeToAddDotSlash = (url, bases = TEST_URL_BASES) => bases.every(base => isSafeToAddDotSlashToUrl(url, base));const isSafeToRemoveDotSlash = (url, bases = TEST_URL_BASES) => bases.every(base => isSafeToAddDotSlashToUrl(url.slice(DOT_SLASH.length), base));function canAddDotSlash(node, context) {	const url = node.value;	if (url.startsWith(DOT_SLASH) || url.startsWith('.') || url.startsWith('/')) {		return false;	}	const baseNode = node.parent.arguments[1];	const staticValueResult = getStaticValue(baseNode, context.getScope());	if (		typeof staticValueResult?.value === 'string'		&& isSafeToAddDotSlash(url, [staticValueResult.value])	) {		return true;	}	return isSafeToAddDotSlash(url);}function canRemoveDotSlash(node, context) {	const rawValue = node.raw.slice(1, -1);	if (!rawValue.startsWith(DOT_SLASH)) {		return false;	}	const baseNode = node.parent.arguments[1];	const staticValueResult = getStaticValue(baseNode, context.getScope());	if (		typeof staticValueResult?.value === 'string'		&& isSafeToRemoveDotSlash(node.value, [staticValueResult.value])	) {		return true;	}	return isSafeToRemoveDotSlash(node.value);}function addDotSlash(node, context) {	if (!canAddDotSlash(node, context)) {		return;	}	return fixer => replaceStringLiteral(fixer, node, DOT_SLASH, 0, 0);}function removeDotSlash(node, context) {	if (!canRemoveDotSlash(node, context)) {		return;	}	return fixer => replaceStringLiteral(fixer, node, '', 0, 2);}/** @param {import('eslint').Rule.RuleContext} context */const create = context => {	const style = context.options[0] || 'never';	const listeners = {};	// TemplateLiteral are not always safe to remove `./`, but if it's starts with `./` we'll report	if (style === 'never') {		listeners[templateLiteralSelector] = function (node) {			const firstPart = node.quasis[0];			if (!firstPart.value.raw.startsWith(DOT_SLASH)) {				return;			}			return {				node,				messageId: style,				suggest: [					{						messageId: MESSAGE_ID_REMOVE,						fix(fixer) {							const start = firstPart.range[0] + 1;							return fixer.removeRange([start, start + 2]);						},					},				],			};		};	}	listeners[literalSelector] = function (node) {		if (typeof node.value !== 'string') {			return;		}		const fix = (style === 'never' ? removeDotSlash : addDotSlash)(node, context);		if (!fix) {			return;		}		return {			node,			messageId: style,			fix,		};	};	return listeners;};const schema = [	{		enum: ['never', 'always'],		default: 'never',	},];/** @type {import('eslint').Rule.RuleModule} */module.exports = {	create,	meta: {		type: 'suggestion',		docs: {			description: 'Enforce consistent relative URL style.',		},		fixable: 'code',		hasSuggestions: true,		schema,		messages,	},};
 |