website refactor
This commit is contained in:
@@ -17,10 +17,61 @@ module.exports = {
|
||||
manualSearchParams: 'Manual URLSearchParams construction. Use SearchParamBuilder instead: import { SearchParamBuilder } from "@/lib/routing/search-params"',
|
||||
manualGetParam: 'Manual search param access with get(). Use SearchParamParser instead: import { SearchParamParser } from "@/lib/routing/search-params"',
|
||||
manualSetParam: 'Manual search param setting with set(). Use SearchParamBuilder instead',
|
||||
manualQueryString:
|
||||
'Manual query-string construction detected (e.g. "?returnTo=..."). Use SearchParamBuilder instead: import { SearchParamBuilder } from "@/lib/routing/search-params/SearchParamBuilder"',
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const SEARCH_PARAM_KEYS = new Set([
|
||||
// Auth
|
||||
'returnTo',
|
||||
'token',
|
||||
'email',
|
||||
'error',
|
||||
'message',
|
||||
// Sponsor
|
||||
'type',
|
||||
'campaignId',
|
||||
// Pagination
|
||||
'page',
|
||||
'limit',
|
||||
'offset',
|
||||
// Sorting
|
||||
'sortBy',
|
||||
'order',
|
||||
// Filters
|
||||
'status',
|
||||
'role',
|
||||
'tier',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Detect patterns like:
|
||||
* - "?returnTo="
|
||||
* - "&returnTo="
|
||||
* - "?page="
|
||||
* - "returnTo=" (within a URL string)
|
||||
*/
|
||||
function containsManualQueryParamFragment(raw) {
|
||||
if (typeof raw !== 'string' || raw.length === 0) return false;
|
||||
|
||||
// Fast pre-check
|
||||
if (!raw.includes('?') && !raw.includes('&') && !raw.includes('=')) return false;
|
||||
|
||||
for (const key of SEARCH_PARAM_KEYS) {
|
||||
if (
|
||||
raw.includes(`?${key}=`) ||
|
||||
raw.includes(`&${key}=`) ||
|
||||
// catches "...returnTo=..." in some string-building scenarios
|
||||
raw.includes(`${key}=`)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
// Detect: new URLSearchParams()
|
||||
NewExpression(node) {
|
||||
@@ -49,6 +100,42 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
// Detect manual query strings, e.g.
|
||||
// `${routes.auth.login}?returnTo=${routes.protected.onboarding}`
|
||||
// routes.auth.login + '?returnTo=' + routes.protected.onboarding
|
||||
TemplateLiteral(node) {
|
||||
// If any static chunk contains a query-param fragment, treat it as manual.
|
||||
for (const quasi of node.quasis) {
|
||||
const raw = quasi.value && (quasi.value.raw ?? quasi.value.cooked);
|
||||
if (containsManualQueryParamFragment(raw)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'manualQueryString',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
BinaryExpression(node) {
|
||||
// String concatenation patterns, e.g. a + '?returnTo=' + b
|
||||
if (node.operator !== '+') return;
|
||||
|
||||
// If either side is a literal string containing query params, report.
|
||||
const left = node.left;
|
||||
const right = node.right;
|
||||
|
||||
if (left && left.type === 'Literal' && typeof left.value === 'string' && containsManualQueryParamFragment(left.value)) {
|
||||
context.report({ node, messageId: 'manualQueryString' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (right && right.type === 'Literal' && typeof right.value === 'string' && containsManualQueryParamFragment(right.value)) {
|
||||
context.report({ node, messageId: 'manualQueryString' });
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
// Detect: params.get() or params.set()
|
||||
CallExpression(node) {
|
||||
if (node.callee.type === 'MemberExpression') {
|
||||
|
||||
Reference in New Issue
Block a user