@gitlab/eslint-plugin
Version:
GitLab package for our custom eslint rules
128 lines (103 loc) • 2.79 kB
JavaScript
const ERROR_MESSAGE =
'Avoid hardcoded URLs. See https://docs.gitlab.com/development/urls_in_gitlab/.';
function traverse(node, type) {
if (node.type === type) {
return node;
}
if (!node.parent) {
return node;
}
return traverse(node.parent, type);
}
function literalValidator(context, node) {
if (isAllowedNode(context, node)) {
return;
}
if (isHardCodedUrl(context, node.value)) {
context.report({
node,
message: ERROR_MESSAGE,
});
}
}
function templateLiteralValidator(context, node) {
if (isAllowedNode(context, node)) {
return;
}
if (isConstructedUrl(context, node)) {
context.report({
node,
message: ERROR_MESSAGE,
});
}
}
function memberExpressionValidator(context, node) {
const { disallowedObjectProperties = [] } = context.options[0] || {};
if (disallowedObjectProperties.includes(node.property?.name)) {
context.report({
node,
message: ERROR_MESSAGE,
});
}
}
function isAllowedNode(context, node) {
const { parent } = node;
const {
allowedKeys = [],
allowedFunctions = [],
allowedVueComponents = [],
} = context.options[0] || {};
if (
parent?.type === 'Property' &&
parent?.key?.type === 'Identifier' &&
allowedKeys.includes(parent?.key?.name)
) {
return true;
}
if (
parent?.type === 'CallExpression' &&
parent?.callee?.type === 'Identifier' &&
allowedFunctions.includes(parent?.callee?.name)
) {
return true;
}
const vElement = traverse(node, 'VElement');
if (vElement && allowedVueComponents.includes(vElement.name)) {
return true;
}
return false;
}
/**
* Check if a string looks like a hardcoded URL path
*/
function isHardCodedUrl(context, value) {
if (typeof value !== 'string') return false;
const { allowedPatterns = [] } = context.options[0] || {};
if (allowedPatterns.some((pattern) => new RegExp(pattern).test(value))) {
return false;
}
// Check for path-like patterns:
// Starts with / followed by word characters
const urlPathPattern = /^\/(?![#?])[\/\.a-zA-Z0-9_-]+/;
return urlPathPattern.test(value);
}
/**
* Check if template literal looks like URL construction
*/
function isConstructedUrl(context, node) {
const { allowedInterpolationVariables = [] } = context.options[0] || {};
if (
node.expressions.some((expression) => allowedInterpolationVariables.includes(expression.name))
) {
return false;
}
// Check if any quasi (string part) looks like a hard coded URL
const hasHardCodedParts = node.quasis.some((quasi) => isHardCodedUrl(context, quasi.value.raw));
return hasHardCodedParts;
}
module.exports = {
literalValidator,
templateLiteralValidator,
memberExpressionValidator,
ERROR_MESSAGE,
};