@stll/oxlint-plugin 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/no-raw-route-query-client.cjs +1 -1
- package/dist/no-raw-route-query-client.cjs.map +1 -1
- package/dist/no-raw-route-query-client.mjs +1 -1
- package/dist/no-raw-route-query-client.mjs.map +1 -1
- package/dist/no-unsafe-inner-html.cjs.map +1 -1
- package/dist/no-unsafe-inner-html.mjs.map +1 -1
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -138,7 +138,7 @@ const plugin = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
-
if (callee.type === "MemberExpression" && callee.computed
|
|
141
|
+
if (callee.type === "MemberExpression" && !callee.computed && require_utils.isIdentifier(callee.object) && routeQueryHelperNamespaces.has(callee.object.name)) {
|
|
142
142
|
const helperName = require_utils.getPropertyName(callee.property);
|
|
143
143
|
const replacement = helperName === null ? void 0 : GENERIC_ROUTE_QUERY_HELPERS.get(helperName);
|
|
144
144
|
if (replacement !== void 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-raw-route-query-client.cjs","names":["getPropertyName","isIdentifier","eslintCompatPlugin","getImportedName"],"sources":["../src/no-raw-route-query-client.ts"],"sourcesContent":["import { eslintCompatPlugin, type Plugin } from \"@oxlint/plugins\";\nimport type { Ranged } from \"@oxlint/plugins\";\n\nimport { getImportedName, getPropertyName, isIdentifier } from \"./utils.ts\";\n\nconst RAW_QUERY_CLIENT_METHODS = new Map([\n [\"ensureQueryData\", \"ensureRouteQueryData\"],\n [\"ensureInfiniteQueryData\", \"ensureRouteInfiniteQueryData\"],\n [\"fetchQuery\", \"fetchRouteQuery\"],\n [\"prefetchQuery\", \"prefetchRouteQuery\"],\n]);\nconst RAW_QUERY_CLIENT_RECEIVERS = new Set([\"qc\", \"queryClient\"]);\nconst TANSTACK_QUERY_MODULE = \"@tanstack/react-query\";\nconst ROUTE_QUERY_HELPERS_MODULE = \"@/lib/react-query\";\nconst GENERIC_ROUTE_QUERY_HELPERS = new Map([\n [\"ensureCriticalQueryData\", \"ensureRouteQueryData\"],\n [\"prefetchNonCriticalQuery\", \"prefetchRouteQuery\"],\n]);\nconst QUERY_SUBSCRIPTION_HOOKS = new Set([\n \"useInfiniteQuery\",\n \"useQueries\",\n \"useQuery\",\n \"useSuspenseInfiniteQuery\",\n \"useSuspenseQueries\",\n \"useSuspenseQuery\",\n]);\n\nconst ROUTE_LOADER_HOOKS = new Set([\"beforeLoad\", \"loader\"]);\nconst ROUTE_PENDING_COMPONENT_HOOK = \"pendingComponent\";\n\nconst isQueryClientReceiver = (node) => {\n if (!node) {\n return false;\n }\n\n if (node.type === \"Identifier\") {\n return RAW_QUERY_CLIENT_RECEIVERS.has(node.name);\n }\n\n if (node.type !== \"MemberExpression\" || node.computed) {\n return false;\n }\n\n const receiverName = getPropertyName(node.property);\n return receiverName === \"queryClient\";\n};\n\nconst findRouteLoaderHook = (node) => {\n let current = node.parent;\n\n while (current) {\n if (current.type === \"Property\") {\n const name = getPropertyName(current.key);\n\n if (name !== null && ROUTE_LOADER_HOOKS.has(name)) {\n return name;\n }\n }\n\n current = current.parent;\n }\n\n return null;\n};\n\nconst findPendingComponentCandidate = (node) => {\n let current = node.parent;\n\n while (current) {\n if (current.type === \"Property\") {\n const name = getPropertyName(current.key);\n\n if (name === ROUTE_PENDING_COMPONENT_HOOK) {\n return ROUTE_PENDING_COMPONENT_HOOK;\n }\n }\n\n if (current.type === \"FunctionDeclaration\" && isIdentifier(current.id)) {\n return current.id.name;\n }\n\n if (current.type === \"VariableDeclarator\" && isIdentifier(current.id)) {\n return current.id.name;\n }\n\n current = current.parent;\n }\n\n return null;\n};\n\nconst getQuerySubscriptionHookName = (\n callee,\n queryHookAliases,\n tanstackQueryNamespaces,\n) => {\n if (isIdentifier(callee) && queryHookAliases.has(callee.name)) {\n return callee.name;\n }\n\n if (\n callee.type !== \"MemberExpression\" ||\n callee.computed ||\n !isIdentifier(callee.object) ||\n !tanstackQueryNamespaces.has(callee.object.name)\n ) {\n return null;\n }\n\n const hookName = getPropertyName(callee.property);\n return hookName !== null && QUERY_SUBSCRIPTION_HOOKS.has(hookName)\n ? hookName\n : null;\n};\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"no-raw-route-query-client\" },\n rules: {\n \"no-raw-route-query-client\": {\n meta: {\n type: \"problem\",\n messages: {\n noRawRouteQueryClient:\n \"Raw queryClient.{{method}} inside route {{hook}} \" +\n \"is forbidden. Use {{replacement}}(...), so route-seeded \" +\n \"queries carry a freshness window.\",\n noGenericRouteQueryHelper:\n \"Generic {{helper}} inside route {{hook}} is forbidden. \" +\n \"Use {{replacement}}(...), so route-seeded queries carry \" +\n \"a freshness window.\",\n noQueryHookInPendingComponent:\n \"TanStack Query subscription hook {{hook}} is forbidden \" +\n \"inside route pendingComponent {{component}}. Read cached \" +\n \"data synchronously with useQueryClient().getQueryData(...) \" +\n \"so abandoned pending renders cannot receive async state updates.\",\n },\n },\n createOnce(context) {\n const genericHelperAliases = new Map();\n const pendingComponentNames = new Set();\n const pendingQueryHookCalls: {\n component: string;\n hook: string;\n node: Ranged;\n }[] = [];\n const queryHookAliases = new Set();\n const routeQueryHelperNamespaces = new Set();\n const tanstackQueryNamespaces = new Set();\n\n return {\n before() {\n genericHelperAliases.clear();\n pendingComponentNames.clear();\n pendingQueryHookCalls.length = 0;\n queryHookAliases.clear();\n routeQueryHelperNamespaces.clear();\n tanstackQueryNamespaces.clear();\n },\n ImportDeclaration(node) {\n if (node.source?.value === TANSTACK_QUERY_MODULE) {\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportNamespaceSpecifier\") {\n tanstackQueryNamespaces.add(specifier.local.name);\n continue;\n }\n if (specifier.type !== \"ImportSpecifier\") {\n continue;\n }\n\n const importedName = getImportedName(specifier);\n if (\n importedName !== null &&\n QUERY_SUBSCRIPTION_HOOKS.has(importedName)\n ) {\n queryHookAliases.add(specifier.local.name);\n }\n }\n return;\n }\n\n if (node.source?.value !== ROUTE_QUERY_HELPERS_MODULE) {\n return;\n }\n\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportNamespaceSpecifier\") {\n routeQueryHelperNamespaces.add(specifier.local.name);\n continue;\n }\n if (specifier.type !== \"ImportSpecifier\") {\n continue;\n }\n\n const importedName = getImportedName(specifier);\n const replacement =\n importedName === null\n ? undefined\n : GENERIC_ROUTE_QUERY_HELPERS.get(importedName);\n if (replacement === undefined) {\n continue;\n }\n\n genericHelperAliases.set(specifier.local.name, {\n helper: importedName,\n replacement,\n });\n }\n },\n\n Property(node) {\n const name = getPropertyName(node.key);\n if (name !== ROUTE_PENDING_COMPONENT_HOOK) {\n return;\n }\n if (isIdentifier(node.value)) {\n pendingComponentNames.add(node.value.name);\n }\n },\n\n CallExpression(node) {\n const hook = findRouteLoaderHook(node);\n const callee = node.callee;\n\n if (hook !== null) {\n if (isIdentifier(callee)) {\n const helper = genericHelperAliases.get(callee.name);\n if (helper !== undefined) {\n context.report({\n node,\n messageId: \"noGenericRouteQueryHelper\",\n data: { hook, ...helper },\n });\n return;\n }\n }\n\n if (\n callee.type === \"MemberExpression\" &&\n callee.computed === false &&\n isIdentifier(callee.object) &&\n routeQueryHelperNamespaces.has(callee.object.name)\n ) {\n const helperName = getPropertyName(callee.property);\n const replacement =\n helperName === null\n ? undefined\n : GENERIC_ROUTE_QUERY_HELPERS.get(helperName);\n\n if (replacement !== undefined) {\n context.report({\n node,\n messageId: \"noGenericRouteQueryHelper\",\n data: { helper: helperName, hook, replacement },\n });\n return;\n }\n }\n\n if (\n callee.type === \"MemberExpression\" &&\n !callee.computed &&\n isQueryClientReceiver(callee.object)\n ) {\n const method = getPropertyName(callee.property);\n const replacement =\n method === null\n ? undefined\n : RAW_QUERY_CLIENT_METHODS.get(method);\n\n if (replacement === undefined) {\n return;\n }\n\n context.report({\n node,\n messageId: \"noRawRouteQueryClient\",\n data: { method, hook, replacement },\n });\n return;\n }\n }\n\n const pendingComponent = findPendingComponentCandidate(node);\n if (pendingComponent === null) {\n return;\n }\n\n const queryHookName = getQuerySubscriptionHookName(\n callee,\n queryHookAliases,\n tanstackQueryNamespaces,\n );\n if (queryHookName === null) {\n return;\n }\n\n pendingQueryHookCalls.push({\n component: pendingComponent,\n hook: queryHookName,\n node,\n });\n },\n\n \"Program:exit\"() {\n for (const { component, hook, node } of pendingQueryHookCalls) {\n if (\n component !== ROUTE_PENDING_COMPONENT_HOOK &&\n !pendingComponentNames.has(component)\n ) {\n continue;\n }\n\n context.report({\n node,\n messageId: \"noQueryHookInPendingComponent\",\n data: { component, hook },\n });\n }\n },\n };\n },\n },\n },\n});\n\nexport default plugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"no-raw-route-query-client.cjs","names":["getPropertyName","isIdentifier","eslintCompatPlugin","getImportedName"],"sources":["../src/no-raw-route-query-client.ts"],"sourcesContent":["import { eslintCompatPlugin, type Plugin, type Ranged } from \"@oxlint/plugins\";\n\nimport { getImportedName, getPropertyName, isIdentifier } from \"./utils.ts\";\n\nconst RAW_QUERY_CLIENT_METHODS = new Map([\n [\"ensureQueryData\", \"ensureRouteQueryData\"],\n [\"ensureInfiniteQueryData\", \"ensureRouteInfiniteQueryData\"],\n [\"fetchQuery\", \"fetchRouteQuery\"],\n [\"prefetchQuery\", \"prefetchRouteQuery\"],\n]);\nconst RAW_QUERY_CLIENT_RECEIVERS = new Set([\"qc\", \"queryClient\"]);\nconst TANSTACK_QUERY_MODULE = \"@tanstack/react-query\";\nconst ROUTE_QUERY_HELPERS_MODULE = \"@/lib/react-query\";\nconst GENERIC_ROUTE_QUERY_HELPERS = new Map([\n [\"ensureCriticalQueryData\", \"ensureRouteQueryData\"],\n [\"prefetchNonCriticalQuery\", \"prefetchRouteQuery\"],\n]);\nconst QUERY_SUBSCRIPTION_HOOKS = new Set([\n \"useInfiniteQuery\",\n \"useQueries\",\n \"useQuery\",\n \"useSuspenseInfiniteQuery\",\n \"useSuspenseQueries\",\n \"useSuspenseQuery\",\n]);\n\nconst ROUTE_LOADER_HOOKS = new Set([\"beforeLoad\", \"loader\"]);\nconst ROUTE_PENDING_COMPONENT_HOOK = \"pendingComponent\";\n\nconst isQueryClientReceiver = (node) => {\n if (!node) {\n return false;\n }\n\n if (node.type === \"Identifier\") {\n return RAW_QUERY_CLIENT_RECEIVERS.has(node.name);\n }\n\n if (node.type !== \"MemberExpression\" || node.computed) {\n return false;\n }\n\n const receiverName = getPropertyName(node.property);\n return receiverName === \"queryClient\";\n};\n\nconst findRouteLoaderHook = (node) => {\n let current = node.parent;\n\n while (current) {\n if (current.type === \"Property\") {\n const name = getPropertyName(current.key);\n\n if (name !== null && ROUTE_LOADER_HOOKS.has(name)) {\n return name;\n }\n }\n\n current = current.parent;\n }\n\n return null;\n};\n\nconst findPendingComponentCandidate = (node) => {\n let current = node.parent;\n\n while (current) {\n if (current.type === \"Property\") {\n const name = getPropertyName(current.key);\n\n if (name === ROUTE_PENDING_COMPONENT_HOOK) {\n return ROUTE_PENDING_COMPONENT_HOOK;\n }\n }\n\n if (current.type === \"FunctionDeclaration\" && isIdentifier(current.id)) {\n return current.id.name;\n }\n\n if (current.type === \"VariableDeclarator\" && isIdentifier(current.id)) {\n return current.id.name;\n }\n\n current = current.parent;\n }\n\n return null;\n};\n\nconst getQuerySubscriptionHookName = (\n callee,\n queryHookAliases,\n tanstackQueryNamespaces,\n) => {\n if (isIdentifier(callee) && queryHookAliases.has(callee.name)) {\n return callee.name;\n }\n\n if (\n callee.type !== \"MemberExpression\" ||\n callee.computed ||\n !isIdentifier(callee.object) ||\n !tanstackQueryNamespaces.has(callee.object.name)\n ) {\n return null;\n }\n\n const hookName = getPropertyName(callee.property);\n return hookName !== null && QUERY_SUBSCRIPTION_HOOKS.has(hookName)\n ? hookName\n : null;\n};\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"no-raw-route-query-client\" },\n rules: {\n \"no-raw-route-query-client\": {\n meta: {\n type: \"problem\",\n messages: {\n noRawRouteQueryClient:\n \"Raw queryClient.{{method}} inside route {{hook}} \" +\n \"is forbidden. Use {{replacement}}(...), so route-seeded \" +\n \"queries carry a freshness window.\",\n noGenericRouteQueryHelper:\n \"Generic {{helper}} inside route {{hook}} is forbidden. \" +\n \"Use {{replacement}}(...), so route-seeded queries carry \" +\n \"a freshness window.\",\n noQueryHookInPendingComponent:\n \"TanStack Query subscription hook {{hook}} is forbidden \" +\n \"inside route pendingComponent {{component}}. Read cached \" +\n \"data synchronously with useQueryClient().getQueryData(...) \" +\n \"so abandoned pending renders cannot receive async state updates.\",\n },\n },\n createOnce(context) {\n const genericHelperAliases = new Map();\n const pendingComponentNames = new Set();\n const pendingQueryHookCalls: {\n component: string;\n hook: string;\n node: Ranged;\n }[] = [];\n const queryHookAliases = new Set();\n const routeQueryHelperNamespaces = new Set();\n const tanstackQueryNamespaces = new Set();\n\n return {\n before() {\n genericHelperAliases.clear();\n pendingComponentNames.clear();\n pendingQueryHookCalls.length = 0;\n queryHookAliases.clear();\n routeQueryHelperNamespaces.clear();\n tanstackQueryNamespaces.clear();\n },\n ImportDeclaration(node) {\n if (node.source?.value === TANSTACK_QUERY_MODULE) {\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportNamespaceSpecifier\") {\n tanstackQueryNamespaces.add(specifier.local.name);\n continue;\n }\n if (specifier.type !== \"ImportSpecifier\") {\n continue;\n }\n\n const importedName = getImportedName(specifier);\n if (\n importedName !== null &&\n QUERY_SUBSCRIPTION_HOOKS.has(importedName)\n ) {\n queryHookAliases.add(specifier.local.name);\n }\n }\n return;\n }\n\n if (node.source?.value !== ROUTE_QUERY_HELPERS_MODULE) {\n return;\n }\n\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportNamespaceSpecifier\") {\n routeQueryHelperNamespaces.add(specifier.local.name);\n continue;\n }\n if (specifier.type !== \"ImportSpecifier\") {\n continue;\n }\n\n const importedName = getImportedName(specifier);\n const replacement =\n importedName === null\n ? undefined\n : GENERIC_ROUTE_QUERY_HELPERS.get(importedName);\n if (replacement === undefined) {\n continue;\n }\n\n genericHelperAliases.set(specifier.local.name, {\n helper: importedName,\n replacement,\n });\n }\n },\n\n Property(node) {\n const name = getPropertyName(node.key);\n if (name !== ROUTE_PENDING_COMPONENT_HOOK) {\n return;\n }\n if (isIdentifier(node.value)) {\n pendingComponentNames.add(node.value.name);\n }\n },\n\n CallExpression(node) {\n const hook = findRouteLoaderHook(node);\n const callee = node.callee;\n\n if (hook !== null) {\n if (isIdentifier(callee)) {\n const helper = genericHelperAliases.get(callee.name);\n if (helper !== undefined) {\n context.report({\n node,\n messageId: \"noGenericRouteQueryHelper\",\n data: { hook, ...helper },\n });\n return;\n }\n }\n\n if (\n callee.type === \"MemberExpression\" &&\n !callee.computed &&\n isIdentifier(callee.object) &&\n routeQueryHelperNamespaces.has(callee.object.name)\n ) {\n const helperName = getPropertyName(callee.property);\n const replacement =\n helperName === null\n ? undefined\n : GENERIC_ROUTE_QUERY_HELPERS.get(helperName);\n\n if (replacement !== undefined) {\n context.report({\n node,\n messageId: \"noGenericRouteQueryHelper\",\n data: { helper: helperName, hook, replacement },\n });\n return;\n }\n }\n\n if (\n callee.type === \"MemberExpression\" &&\n !callee.computed &&\n isQueryClientReceiver(callee.object)\n ) {\n const method = getPropertyName(callee.property);\n const replacement =\n method === null\n ? undefined\n : RAW_QUERY_CLIENT_METHODS.get(method);\n\n if (replacement === undefined) {\n return;\n }\n\n context.report({\n node,\n messageId: \"noRawRouteQueryClient\",\n data: { method, hook, replacement },\n });\n return;\n }\n }\n\n const pendingComponent = findPendingComponentCandidate(node);\n if (pendingComponent === null) {\n return;\n }\n\n const queryHookName = getQuerySubscriptionHookName(\n callee,\n queryHookAliases,\n tanstackQueryNamespaces,\n );\n if (queryHookName === null) {\n return;\n }\n\n pendingQueryHookCalls.push({\n component: pendingComponent,\n hook: queryHookName,\n node,\n });\n },\n\n \"Program:exit\"() {\n for (const { component, hook, node } of pendingQueryHookCalls) {\n if (\n component !== ROUTE_PENDING_COMPONENT_HOOK &&\n !pendingComponentNames.has(component)\n ) {\n continue;\n }\n\n context.report({\n node,\n messageId: \"noQueryHookInPendingComponent\",\n data: { component, hook },\n });\n }\n },\n };\n },\n },\n },\n});\n\nexport default plugin;\n"],"mappings":"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"}
|
|
@@ -134,7 +134,7 @@ const plugin = eslintCompatPlugin({
|
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
if (callee.type === "MemberExpression" && callee.computed
|
|
137
|
+
if (callee.type === "MemberExpression" && !callee.computed && isIdentifier(callee.object) && routeQueryHelperNamespaces.has(callee.object.name)) {
|
|
138
138
|
const helperName = getPropertyName(callee.property);
|
|
139
139
|
const replacement = helperName === null ? void 0 : GENERIC_ROUTE_QUERY_HELPERS.get(helperName);
|
|
140
140
|
if (replacement !== void 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-raw-route-query-client.mjs","names":[],"sources":["../src/no-raw-route-query-client.ts"],"sourcesContent":["import { eslintCompatPlugin, type Plugin } from \"@oxlint/plugins\";\nimport type { Ranged } from \"@oxlint/plugins\";\n\nimport { getImportedName, getPropertyName, isIdentifier } from \"./utils.ts\";\n\nconst RAW_QUERY_CLIENT_METHODS = new Map([\n [\"ensureQueryData\", \"ensureRouteQueryData\"],\n [\"ensureInfiniteQueryData\", \"ensureRouteInfiniteQueryData\"],\n [\"fetchQuery\", \"fetchRouteQuery\"],\n [\"prefetchQuery\", \"prefetchRouteQuery\"],\n]);\nconst RAW_QUERY_CLIENT_RECEIVERS = new Set([\"qc\", \"queryClient\"]);\nconst TANSTACK_QUERY_MODULE = \"@tanstack/react-query\";\nconst ROUTE_QUERY_HELPERS_MODULE = \"@/lib/react-query\";\nconst GENERIC_ROUTE_QUERY_HELPERS = new Map([\n [\"ensureCriticalQueryData\", \"ensureRouteQueryData\"],\n [\"prefetchNonCriticalQuery\", \"prefetchRouteQuery\"],\n]);\nconst QUERY_SUBSCRIPTION_HOOKS = new Set([\n \"useInfiniteQuery\",\n \"useQueries\",\n \"useQuery\",\n \"useSuspenseInfiniteQuery\",\n \"useSuspenseQueries\",\n \"useSuspenseQuery\",\n]);\n\nconst ROUTE_LOADER_HOOKS = new Set([\"beforeLoad\", \"loader\"]);\nconst ROUTE_PENDING_COMPONENT_HOOK = \"pendingComponent\";\n\nconst isQueryClientReceiver = (node) => {\n if (!node) {\n return false;\n }\n\n if (node.type === \"Identifier\") {\n return RAW_QUERY_CLIENT_RECEIVERS.has(node.name);\n }\n\n if (node.type !== \"MemberExpression\" || node.computed) {\n return false;\n }\n\n const receiverName = getPropertyName(node.property);\n return receiverName === \"queryClient\";\n};\n\nconst findRouteLoaderHook = (node) => {\n let current = node.parent;\n\n while (current) {\n if (current.type === \"Property\") {\n const name = getPropertyName(current.key);\n\n if (name !== null && ROUTE_LOADER_HOOKS.has(name)) {\n return name;\n }\n }\n\n current = current.parent;\n }\n\n return null;\n};\n\nconst findPendingComponentCandidate = (node) => {\n let current = node.parent;\n\n while (current) {\n if (current.type === \"Property\") {\n const name = getPropertyName(current.key);\n\n if (name === ROUTE_PENDING_COMPONENT_HOOK) {\n return ROUTE_PENDING_COMPONENT_HOOK;\n }\n }\n\n if (current.type === \"FunctionDeclaration\" && isIdentifier(current.id)) {\n return current.id.name;\n }\n\n if (current.type === \"VariableDeclarator\" && isIdentifier(current.id)) {\n return current.id.name;\n }\n\n current = current.parent;\n }\n\n return null;\n};\n\nconst getQuerySubscriptionHookName = (\n callee,\n queryHookAliases,\n tanstackQueryNamespaces,\n) => {\n if (isIdentifier(callee) && queryHookAliases.has(callee.name)) {\n return callee.name;\n }\n\n if (\n callee.type !== \"MemberExpression\" ||\n callee.computed ||\n !isIdentifier(callee.object) ||\n !tanstackQueryNamespaces.has(callee.object.name)\n ) {\n return null;\n }\n\n const hookName = getPropertyName(callee.property);\n return hookName !== null && QUERY_SUBSCRIPTION_HOOKS.has(hookName)\n ? hookName\n : null;\n};\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"no-raw-route-query-client\" },\n rules: {\n \"no-raw-route-query-client\": {\n meta: {\n type: \"problem\",\n messages: {\n noRawRouteQueryClient:\n \"Raw queryClient.{{method}} inside route {{hook}} \" +\n \"is forbidden. Use {{replacement}}(...), so route-seeded \" +\n \"queries carry a freshness window.\",\n noGenericRouteQueryHelper:\n \"Generic {{helper}} inside route {{hook}} is forbidden. \" +\n \"Use {{replacement}}(...), so route-seeded queries carry \" +\n \"a freshness window.\",\n noQueryHookInPendingComponent:\n \"TanStack Query subscription hook {{hook}} is forbidden \" +\n \"inside route pendingComponent {{component}}. Read cached \" +\n \"data synchronously with useQueryClient().getQueryData(...) \" +\n \"so abandoned pending renders cannot receive async state updates.\",\n },\n },\n createOnce(context) {\n const genericHelperAliases = new Map();\n const pendingComponentNames = new Set();\n const pendingQueryHookCalls: {\n component: string;\n hook: string;\n node: Ranged;\n }[] = [];\n const queryHookAliases = new Set();\n const routeQueryHelperNamespaces = new Set();\n const tanstackQueryNamespaces = new Set();\n\n return {\n before() {\n genericHelperAliases.clear();\n pendingComponentNames.clear();\n pendingQueryHookCalls.length = 0;\n queryHookAliases.clear();\n routeQueryHelperNamespaces.clear();\n tanstackQueryNamespaces.clear();\n },\n ImportDeclaration(node) {\n if (node.source?.value === TANSTACK_QUERY_MODULE) {\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportNamespaceSpecifier\") {\n tanstackQueryNamespaces.add(specifier.local.name);\n continue;\n }\n if (specifier.type !== \"ImportSpecifier\") {\n continue;\n }\n\n const importedName = getImportedName(specifier);\n if (\n importedName !== null &&\n QUERY_SUBSCRIPTION_HOOKS.has(importedName)\n ) {\n queryHookAliases.add(specifier.local.name);\n }\n }\n return;\n }\n\n if (node.source?.value !== ROUTE_QUERY_HELPERS_MODULE) {\n return;\n }\n\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportNamespaceSpecifier\") {\n routeQueryHelperNamespaces.add(specifier.local.name);\n continue;\n }\n if (specifier.type !== \"ImportSpecifier\") {\n continue;\n }\n\n const importedName = getImportedName(specifier);\n const replacement =\n importedName === null\n ? undefined\n : GENERIC_ROUTE_QUERY_HELPERS.get(importedName);\n if (replacement === undefined) {\n continue;\n }\n\n genericHelperAliases.set(specifier.local.name, {\n helper: importedName,\n replacement,\n });\n }\n },\n\n Property(node) {\n const name = getPropertyName(node.key);\n if (name !== ROUTE_PENDING_COMPONENT_HOOK) {\n return;\n }\n if (isIdentifier(node.value)) {\n pendingComponentNames.add(node.value.name);\n }\n },\n\n CallExpression(node) {\n const hook = findRouteLoaderHook(node);\n const callee = node.callee;\n\n if (hook !== null) {\n if (isIdentifier(callee)) {\n const helper = genericHelperAliases.get(callee.name);\n if (helper !== undefined) {\n context.report({\n node,\n messageId: \"noGenericRouteQueryHelper\",\n data: { hook, ...helper },\n });\n return;\n }\n }\n\n if (\n callee.type === \"MemberExpression\" &&\n callee.computed === false &&\n isIdentifier(callee.object) &&\n routeQueryHelperNamespaces.has(callee.object.name)\n ) {\n const helperName = getPropertyName(callee.property);\n const replacement =\n helperName === null\n ? undefined\n : GENERIC_ROUTE_QUERY_HELPERS.get(helperName);\n\n if (replacement !== undefined) {\n context.report({\n node,\n messageId: \"noGenericRouteQueryHelper\",\n data: { helper: helperName, hook, replacement },\n });\n return;\n }\n }\n\n if (\n callee.type === \"MemberExpression\" &&\n !callee.computed &&\n isQueryClientReceiver(callee.object)\n ) {\n const method = getPropertyName(callee.property);\n const replacement =\n method === null\n ? undefined\n : RAW_QUERY_CLIENT_METHODS.get(method);\n\n if (replacement === undefined) {\n return;\n }\n\n context.report({\n node,\n messageId: \"noRawRouteQueryClient\",\n data: { method, hook, replacement },\n });\n return;\n }\n }\n\n const pendingComponent = findPendingComponentCandidate(node);\n if (pendingComponent === null) {\n return;\n }\n\n const queryHookName = getQuerySubscriptionHookName(\n callee,\n queryHookAliases,\n tanstackQueryNamespaces,\n );\n if (queryHookName === null) {\n return;\n }\n\n pendingQueryHookCalls.push({\n component: pendingComponent,\n hook: queryHookName,\n node,\n });\n },\n\n \"Program:exit\"() {\n for (const { component, hook, node } of pendingQueryHookCalls) {\n if (\n component !== ROUTE_PENDING_COMPONENT_HOOK &&\n !pendingComponentNames.has(component)\n ) {\n continue;\n }\n\n context.report({\n node,\n messageId: \"noQueryHookInPendingComponent\",\n data: { component, hook },\n });\n }\n },\n };\n },\n },\n },\n});\n\nexport default plugin;\n"],"mappings":";;;AAKA,MAAM,2CAA2B,IAAI,IAAI;CACvC,CAAC,mBAAmB,sBAAsB;CAC1C,CAAC,2BAA2B,8BAA8B;CAC1D,CAAC,cAAc,iBAAiB;CAChC,CAAC,iBAAiB,oBAAoB;AACxC,CAAC;AACD,MAAM,6CAA6B,IAAI,IAAI,CAAC,MAAM,aAAa,CAAC;AAChE,MAAM,wBAAwB;AAC9B,MAAM,6BAA6B;AACnC,MAAM,8CAA8B,IAAI,IAAI,CAC1C,CAAC,2BAA2B,sBAAsB,GAClD,CAAC,4BAA4B,oBAAoB,CACnD,CAAC;AACD,MAAM,2CAA2B,IAAI,IAAI;CACvC;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,qCAAqB,IAAI,IAAI,CAAC,cAAc,QAAQ,CAAC;AAC3D,MAAM,+BAA+B;AAErC,MAAM,yBAAyB,SAAS;CACtC,IAAI,CAAC,MACH,OAAO;CAGT,IAAI,KAAK,SAAS,cAChB,OAAO,2BAA2B,IAAI,KAAK,IAAI;CAGjD,IAAI,KAAK,SAAS,sBAAsB,KAAK,UAC3C,OAAO;CAIT,OADqB,gBAAgB,KAAK,QACxB,MAAM;AAC1B;AAEA,MAAM,uBAAuB,SAAS;CACpC,IAAI,UAAU,KAAK;CAEnB,OAAO,SAAS;EACd,IAAI,QAAQ,SAAS,YAAY;GAC/B,MAAM,OAAO,gBAAgB,QAAQ,GAAG;GAExC,IAAI,SAAS,QAAQ,mBAAmB,IAAI,IAAI,GAC9C,OAAO;EAEX;EAEA,UAAU,QAAQ;CACpB;CAEA,OAAO;AACT;AAEA,MAAM,iCAAiC,SAAS;CAC9C,IAAI,UAAU,KAAK;CAEnB,OAAO,SAAS;EACd,IAAI,QAAQ,SAAS,YACN;OAAA,gBAAgB,QAAQ,GAE9B,MAAM,8BACX,OAAO;EAAA;EAIX,IAAI,QAAQ,SAAS,yBAAyB,aAAa,QAAQ,EAAE,GACnE,OAAO,QAAQ,GAAG;EAGpB,IAAI,QAAQ,SAAS,wBAAwB,aAAa,QAAQ,EAAE,GAClE,OAAO,QAAQ,GAAG;EAGpB,UAAU,QAAQ;CACpB;CAEA,OAAO;AACT;AAEA,MAAM,gCACJ,QACA,kBACA,4BACG;CACH,IAAI,aAAa,MAAM,KAAK,iBAAiB,IAAI,OAAO,IAAI,GAC1D,OAAO,OAAO;CAGhB,IACE,OAAO,SAAS,sBAChB,OAAO,YACP,CAAC,aAAa,OAAO,MAAM,KAC3B,CAAC,wBAAwB,IAAI,OAAO,OAAO,IAAI,GAE/C,OAAO;CAGT,MAAM,WAAW,gBAAgB,OAAO,QAAQ;CAChD,OAAO,aAAa,QAAQ,yBAAyB,IAAI,QAAQ,IAC7D,WACA;AACN;AAEA,MAAM,SAAiB,mBAAmB;CACxC,MAAM,EAAE,MAAM,4BAA4B;CAC1C,OAAO,EACL,6BAA6B;EAC3B,MAAM;GACJ,MAAM;GACN,UAAU;IACR,uBACE;IAGF,2BACE;IAGF,+BACE;GAIJ;EACF;EACA,WAAW,SAAS;GAClB,MAAM,uCAAuB,IAAI,IAAI;GACrC,MAAM,wCAAwB,IAAI,IAAI;GACtC,MAAM,wBAIA,CAAC;GACP,MAAM,mCAAmB,IAAI,IAAI;GACjC,MAAM,6CAA6B,IAAI,IAAI;GAC3C,MAAM,0CAA0B,IAAI,IAAI;GAExC,OAAO;IACL,SAAS;KACP,qBAAqB,MAAM;KAC3B,sBAAsB,MAAM;KAC5B,sBAAsB,SAAS;KAC/B,iBAAiB,MAAM;KACvB,2BAA2B,MAAM;KACjC,wBAAwB,MAAM;IAChC;IACA,kBAAkB,MAAM;KACtB,IAAI,KAAK,QAAQ,UAAU,uBAAuB;MAChD,KAAK,MAAM,aAAa,KAAK,YAAY;OACvC,IAAI,UAAU,SAAS,4BAA4B;QACjD,wBAAwB,IAAI,UAAU,MAAM,IAAI;QAChD;OACF;OACA,IAAI,UAAU,SAAS,mBACrB;OAGF,MAAM,eAAe,gBAAgB,SAAS;OAC9C,IACE,iBAAiB,QACjB,yBAAyB,IAAI,YAAY,GAEzC,iBAAiB,IAAI,UAAU,MAAM,IAAI;MAE7C;MACA;KACF;KAEA,IAAI,KAAK,QAAQ,UAAU,4BACzB;KAGF,KAAK,MAAM,aAAa,KAAK,YAAY;MACvC,IAAI,UAAU,SAAS,4BAA4B;OACjD,2BAA2B,IAAI,UAAU,MAAM,IAAI;OACnD;MACF;MACA,IAAI,UAAU,SAAS,mBACrB;MAGF,MAAM,eAAe,gBAAgB,SAAS;MAC9C,MAAM,cACJ,iBAAiB,OACb,KAAA,IACA,4BAA4B,IAAI,YAAY;MAClD,IAAI,gBAAgB,KAAA,GAClB;MAGF,qBAAqB,IAAI,UAAU,MAAM,MAAM;OAC7C,QAAQ;OACR;MACF,CAAC;KACH;IACF;IAEA,SAAS,MAAM;KAEb,IADa,gBAAgB,KAAK,GAC3B,MAAM,8BACX;KAEF,IAAI,aAAa,KAAK,KAAK,GACzB,sBAAsB,IAAI,KAAK,MAAM,IAAI;IAE7C;IAEA,eAAe,MAAM;KACnB,MAAM,OAAO,oBAAoB,IAAI;KACrC,MAAM,SAAS,KAAK;KAEpB,IAAI,SAAS,MAAM;MACjB,IAAI,aAAa,MAAM,GAAG;OACxB,MAAM,SAAS,qBAAqB,IAAI,OAAO,IAAI;OACnD,IAAI,WAAW,KAAA,GAAW;QACxB,QAAQ,OAAO;SACb;SACA,WAAW;SACX,MAAM;UAAE;UAAM,GAAG;SAAO;QAC1B,CAAC;QACD;OACF;MACF;MAEA,IACE,OAAO,SAAS,sBAChB,OAAO,aAAa,SACpB,aAAa,OAAO,MAAM,KAC1B,2BAA2B,IAAI,OAAO,OAAO,IAAI,GACjD;OACA,MAAM,aAAa,gBAAgB,OAAO,QAAQ;OAClD,MAAM,cACJ,eAAe,OACX,KAAA,IACA,4BAA4B,IAAI,UAAU;OAEhD,IAAI,gBAAgB,KAAA,GAAW;QAC7B,QAAQ,OAAO;SACb;SACA,WAAW;SACX,MAAM;UAAE,QAAQ;UAAY;UAAM;SAAY;QAChD,CAAC;QACD;OACF;MACF;MAEA,IACE,OAAO,SAAS,sBAChB,CAAC,OAAO,YACR,sBAAsB,OAAO,MAAM,GACnC;OACA,MAAM,SAAS,gBAAgB,OAAO,QAAQ;OAC9C,MAAM,cACJ,WAAW,OACP,KAAA,IACA,yBAAyB,IAAI,MAAM;OAEzC,IAAI,gBAAgB,KAAA,GAClB;OAGF,QAAQ,OAAO;QACb;QACA,WAAW;QACX,MAAM;SAAE;SAAQ;SAAM;QAAY;OACpC,CAAC;OACD;MACF;KACF;KAEA,MAAM,mBAAmB,8BAA8B,IAAI;KAC3D,IAAI,qBAAqB,MACvB;KAGF,MAAM,gBAAgB,6BACpB,QACA,kBACA,uBACF;KACA,IAAI,kBAAkB,MACpB;KAGF,sBAAsB,KAAK;MACzB,WAAW;MACX,MAAM;MACN;KACF,CAAC;IACH;IAEA,iBAAiB;KACf,KAAK,MAAM,EAAE,WAAW,MAAM,UAAU,uBAAuB;MAC7D,IACE,cAAc,gCACd,CAAC,sBAAsB,IAAI,SAAS,GAEpC;MAGF,QAAQ,OAAO;OACb;OACA,WAAW;OACX,MAAM;QAAE;QAAW;OAAK;MAC1B,CAAC;KACH;IACF;GACF;EACF;CACF,EACF;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"no-raw-route-query-client.mjs","names":[],"sources":["../src/no-raw-route-query-client.ts"],"sourcesContent":["import { eslintCompatPlugin, type Plugin, type Ranged } from \"@oxlint/plugins\";\n\nimport { getImportedName, getPropertyName, isIdentifier } from \"./utils.ts\";\n\nconst RAW_QUERY_CLIENT_METHODS = new Map([\n [\"ensureQueryData\", \"ensureRouteQueryData\"],\n [\"ensureInfiniteQueryData\", \"ensureRouteInfiniteQueryData\"],\n [\"fetchQuery\", \"fetchRouteQuery\"],\n [\"prefetchQuery\", \"prefetchRouteQuery\"],\n]);\nconst RAW_QUERY_CLIENT_RECEIVERS = new Set([\"qc\", \"queryClient\"]);\nconst TANSTACK_QUERY_MODULE = \"@tanstack/react-query\";\nconst ROUTE_QUERY_HELPERS_MODULE = \"@/lib/react-query\";\nconst GENERIC_ROUTE_QUERY_HELPERS = new Map([\n [\"ensureCriticalQueryData\", \"ensureRouteQueryData\"],\n [\"prefetchNonCriticalQuery\", \"prefetchRouteQuery\"],\n]);\nconst QUERY_SUBSCRIPTION_HOOKS = new Set([\n \"useInfiniteQuery\",\n \"useQueries\",\n \"useQuery\",\n \"useSuspenseInfiniteQuery\",\n \"useSuspenseQueries\",\n \"useSuspenseQuery\",\n]);\n\nconst ROUTE_LOADER_HOOKS = new Set([\"beforeLoad\", \"loader\"]);\nconst ROUTE_PENDING_COMPONENT_HOOK = \"pendingComponent\";\n\nconst isQueryClientReceiver = (node) => {\n if (!node) {\n return false;\n }\n\n if (node.type === \"Identifier\") {\n return RAW_QUERY_CLIENT_RECEIVERS.has(node.name);\n }\n\n if (node.type !== \"MemberExpression\" || node.computed) {\n return false;\n }\n\n const receiverName = getPropertyName(node.property);\n return receiverName === \"queryClient\";\n};\n\nconst findRouteLoaderHook = (node) => {\n let current = node.parent;\n\n while (current) {\n if (current.type === \"Property\") {\n const name = getPropertyName(current.key);\n\n if (name !== null && ROUTE_LOADER_HOOKS.has(name)) {\n return name;\n }\n }\n\n current = current.parent;\n }\n\n return null;\n};\n\nconst findPendingComponentCandidate = (node) => {\n let current = node.parent;\n\n while (current) {\n if (current.type === \"Property\") {\n const name = getPropertyName(current.key);\n\n if (name === ROUTE_PENDING_COMPONENT_HOOK) {\n return ROUTE_PENDING_COMPONENT_HOOK;\n }\n }\n\n if (current.type === \"FunctionDeclaration\" && isIdentifier(current.id)) {\n return current.id.name;\n }\n\n if (current.type === \"VariableDeclarator\" && isIdentifier(current.id)) {\n return current.id.name;\n }\n\n current = current.parent;\n }\n\n return null;\n};\n\nconst getQuerySubscriptionHookName = (\n callee,\n queryHookAliases,\n tanstackQueryNamespaces,\n) => {\n if (isIdentifier(callee) && queryHookAliases.has(callee.name)) {\n return callee.name;\n }\n\n if (\n callee.type !== \"MemberExpression\" ||\n callee.computed ||\n !isIdentifier(callee.object) ||\n !tanstackQueryNamespaces.has(callee.object.name)\n ) {\n return null;\n }\n\n const hookName = getPropertyName(callee.property);\n return hookName !== null && QUERY_SUBSCRIPTION_HOOKS.has(hookName)\n ? hookName\n : null;\n};\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"no-raw-route-query-client\" },\n rules: {\n \"no-raw-route-query-client\": {\n meta: {\n type: \"problem\",\n messages: {\n noRawRouteQueryClient:\n \"Raw queryClient.{{method}} inside route {{hook}} \" +\n \"is forbidden. Use {{replacement}}(...), so route-seeded \" +\n \"queries carry a freshness window.\",\n noGenericRouteQueryHelper:\n \"Generic {{helper}} inside route {{hook}} is forbidden. \" +\n \"Use {{replacement}}(...), so route-seeded queries carry \" +\n \"a freshness window.\",\n noQueryHookInPendingComponent:\n \"TanStack Query subscription hook {{hook}} is forbidden \" +\n \"inside route pendingComponent {{component}}. Read cached \" +\n \"data synchronously with useQueryClient().getQueryData(...) \" +\n \"so abandoned pending renders cannot receive async state updates.\",\n },\n },\n createOnce(context) {\n const genericHelperAliases = new Map();\n const pendingComponentNames = new Set();\n const pendingQueryHookCalls: {\n component: string;\n hook: string;\n node: Ranged;\n }[] = [];\n const queryHookAliases = new Set();\n const routeQueryHelperNamespaces = new Set();\n const tanstackQueryNamespaces = new Set();\n\n return {\n before() {\n genericHelperAliases.clear();\n pendingComponentNames.clear();\n pendingQueryHookCalls.length = 0;\n queryHookAliases.clear();\n routeQueryHelperNamespaces.clear();\n tanstackQueryNamespaces.clear();\n },\n ImportDeclaration(node) {\n if (node.source?.value === TANSTACK_QUERY_MODULE) {\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportNamespaceSpecifier\") {\n tanstackQueryNamespaces.add(specifier.local.name);\n continue;\n }\n if (specifier.type !== \"ImportSpecifier\") {\n continue;\n }\n\n const importedName = getImportedName(specifier);\n if (\n importedName !== null &&\n QUERY_SUBSCRIPTION_HOOKS.has(importedName)\n ) {\n queryHookAliases.add(specifier.local.name);\n }\n }\n return;\n }\n\n if (node.source?.value !== ROUTE_QUERY_HELPERS_MODULE) {\n return;\n }\n\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportNamespaceSpecifier\") {\n routeQueryHelperNamespaces.add(specifier.local.name);\n continue;\n }\n if (specifier.type !== \"ImportSpecifier\") {\n continue;\n }\n\n const importedName = getImportedName(specifier);\n const replacement =\n importedName === null\n ? undefined\n : GENERIC_ROUTE_QUERY_HELPERS.get(importedName);\n if (replacement === undefined) {\n continue;\n }\n\n genericHelperAliases.set(specifier.local.name, {\n helper: importedName,\n replacement,\n });\n }\n },\n\n Property(node) {\n const name = getPropertyName(node.key);\n if (name !== ROUTE_PENDING_COMPONENT_HOOK) {\n return;\n }\n if (isIdentifier(node.value)) {\n pendingComponentNames.add(node.value.name);\n }\n },\n\n CallExpression(node) {\n const hook = findRouteLoaderHook(node);\n const callee = node.callee;\n\n if (hook !== null) {\n if (isIdentifier(callee)) {\n const helper = genericHelperAliases.get(callee.name);\n if (helper !== undefined) {\n context.report({\n node,\n messageId: \"noGenericRouteQueryHelper\",\n data: { hook, ...helper },\n });\n return;\n }\n }\n\n if (\n callee.type === \"MemberExpression\" &&\n !callee.computed &&\n isIdentifier(callee.object) &&\n routeQueryHelperNamespaces.has(callee.object.name)\n ) {\n const helperName = getPropertyName(callee.property);\n const replacement =\n helperName === null\n ? undefined\n : GENERIC_ROUTE_QUERY_HELPERS.get(helperName);\n\n if (replacement !== undefined) {\n context.report({\n node,\n messageId: \"noGenericRouteQueryHelper\",\n data: { helper: helperName, hook, replacement },\n });\n return;\n }\n }\n\n if (\n callee.type === \"MemberExpression\" &&\n !callee.computed &&\n isQueryClientReceiver(callee.object)\n ) {\n const method = getPropertyName(callee.property);\n const replacement =\n method === null\n ? undefined\n : RAW_QUERY_CLIENT_METHODS.get(method);\n\n if (replacement === undefined) {\n return;\n }\n\n context.report({\n node,\n messageId: \"noRawRouteQueryClient\",\n data: { method, hook, replacement },\n });\n return;\n }\n }\n\n const pendingComponent = findPendingComponentCandidate(node);\n if (pendingComponent === null) {\n return;\n }\n\n const queryHookName = getQuerySubscriptionHookName(\n callee,\n queryHookAliases,\n tanstackQueryNamespaces,\n );\n if (queryHookName === null) {\n return;\n }\n\n pendingQueryHookCalls.push({\n component: pendingComponent,\n hook: queryHookName,\n node,\n });\n },\n\n \"Program:exit\"() {\n for (const { component, hook, node } of pendingQueryHookCalls) {\n if (\n component !== ROUTE_PENDING_COMPONENT_HOOK &&\n !pendingComponentNames.has(component)\n ) {\n continue;\n }\n\n context.report({\n node,\n messageId: \"noQueryHookInPendingComponent\",\n data: { component, hook },\n });\n }\n },\n };\n },\n },\n },\n});\n\nexport default plugin;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-unsafe-inner-html.cjs","names":["unwrapExpression","isIdentifier","memberPropertyName","isAstNode","isStringLiteral","staticStringValue","getPropertyName","resolveLiteralArraySpreads","eslintCompatPlugin","invokedCallee","exhaustive"],"sources":["../src/no-unsafe-inner-html.ts"],"sourcesContent":["// Forbid injecting un-proven HTML into the DOM.\n//\n// Raw HTML may only reach the DOM from a value that is provably sanitized or\n// escaped. This rule covers common DOM and JSX sinks while allowing explicit\n// sanitizer calls and narrowly scoped provenance comments.\n//\n// Sinks, all sharing one allowlist:\n// • `__html` of a `dangerouslySetInnerHTML` object, whether the object is a\n// JSX attribute value or a property of any object literal (props passed\n// to `createElement` / `jsx()`, or spread into JSX).\n// • assignment to `innerHTML`, `outerHTML` or `srcdoc`, dot or static\n// bracket notation, and the same keys in an `Object.assign` source\n// (either branch of a conditional or logical source).\n// • the JSX `srcDoc` attribute.\n// • `insertAdjacentHTML`, `setHTMLUnsafe`, `createContextualFragment`,\n// `setAttribute(\"srcdoc\", …)` / `setAttributeNS(…, \"srcdoc\", …)`,\n// and `write` / `writeln` on a document.\n//\n// In the default `allowlist` policy, a value is allowed when it is:\n// • a static string Literal or TemplateLiteral whose interpolations are all\n// independently static, OR\n// • passed through the shared HTML sanitizer allowlist (`escapeHtml`,\n// `escapeXml`, `sanitizeHtml`, `sanitizeDom`, `DOMPurify.sanitize`, and\n// related established names), including `.value` / `.data` / `.html`\n// result accessors; OR\n// • annotated by a `// safe-html: <provenance>` comment with non-empty text\n// on the line directly above the sink. One comment covers one sink: a\n// second sink starting on the same line needs its own annotation.\n// The `provenance` policy omits the sanitizer-name allowlist and requires the\n// comment for dynamic sanitizer results as well.\n//\n// The sanitizer allowlist preserves the shared plugin contract. For dynamic\n// values outside that list, function names are not proof: a local identity\n// function can be named `sanitizeHtml`. Add explicit provenance at the sink\n// until the codebase has a branded SafeHtml boundary.\n\nimport type { Scope, Variable } from \"@oxlint/plugins\";\nimport { eslintCompatPlugin, type Plugin } from \"@oxlint/plugins\";\n\nimport type { AstNode, LiteralArgumentResolution } from \"./utils.ts\";\nimport {\n getPropertyName,\n invokedCallee,\n isAstNode,\n isIdentifier,\n isStringLiteral,\n memberPropertyName,\n resolveLiteralArraySpreads,\n staticStringValue,\n unwrapExpression,\n} from \"./utils.ts\";\n\ntype DomElementProducer = {\n name: string;\n kind: \"property\" | \"method\" | \"collection\" | \"collection-property\";\n receiver: \"document\" | \"element\" | \"either\";\n};\n\n// DOM element provenance and the generated equivalence corpus share this table.\nexport const DOM_ELEMENT_PRODUCERS = [\n { name: \"body\", kind: \"property\", receiver: \"document\" },\n { name: \"head\", kind: \"property\", receiver: \"document\" },\n { name: \"documentElement\", kind: \"property\", receiver: \"document\" },\n { name: \"activeElement\", kind: \"property\", receiver: \"document\" },\n { name: \"scrollingElement\", kind: \"property\", receiver: \"document\" },\n { name: \"fullscreenElement\", kind: \"property\", receiver: \"document\" },\n { name: \"createElement\", kind: \"method\", receiver: \"document\" },\n { name: \"createElementNS\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementById\", kind: \"method\", receiver: \"document\" },\n { name: \"querySelector\", kind: \"method\", receiver: \"either\" },\n { name: \"closest\", kind: \"method\", receiver: \"element\" },\n { name: \"elementFromPoint\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementsByClassName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagNameNS\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByName\", kind: \"collection\", receiver: \"document\" },\n { name: \"querySelectorAll\", kind: \"collection\", receiver: \"either\" },\n { name: \"children\", kind: \"collection-property\", receiver: \"element\" },\n { name: \"parentElement\", kind: \"property\", receiver: \"element\" },\n { name: \"firstElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"lastElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"nextElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"previousElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"offsetParent\", kind: \"property\", receiver: \"element\" },\n] as const;\n\n// The provenance text after the marker must be non-empty.\nconst WAIVER_RE = /^\\s*safe-html:\\s*\\S/u;\n\nexport const REACT_HTML_PROPS = [\n { name: \"dangerouslySetInnerHTML\", kind: \"payload\", htmlKey: \"__html\" },\n { name: \"srcDoc\", kind: \"value\" },\n { name: \"srcdoc\", kind: \"value\" },\n] as const;\ntype ReactHtmlProp = (typeof REACT_HTML_PROPS)[number];\nexport const REACT_ELEMENT_FACTORIES: ReadonlySet<string> = new Set([\n \"createElement\",\n \"React.createElement\",\n \"jsx\",\n \"jsxs\",\n \"jsxDEV\",\n]);\nconst HTML_PROPERTIES: ReadonlySet<string> = new Set([\n \"innerHTML\",\n \"outerHTML\",\n \"srcdoc\",\n]);\n// Methods whose first argument is parsed as HTML.\nconst HTML_FIRST_ARGUMENT_METHODS: ReadonlySet<string> = new Set([\n \"createContextualFragment\",\n \"setHTMLUnsafe\",\n]);\nexport const DOCUMENT_WRITE_METHODS: ReadonlySet<string> = new Set([\n \"write\",\n \"writeln\",\n]);\nexport const HTML_ATTRIBUTE_SINKS = [\n { name: \"setAttribute\", namespaceIndex: null, nameIndex: 0, valueIndex: 1 },\n { name: \"setAttributeNS\", namespaceIndex: 0, nameIndex: 1, valueIndex: 2 },\n] as const;\nconst DOCUMENT_MEMBERS: ReadonlySet<string> = new Set([\n \"contentDocument\",\n \"document\",\n \"ownerDocument\",\n]);\nconst FILE_SYSTEM_MODULES: ReadonlySet<string> = new Set([\n \"fs\",\n \"fs/promises\",\n \"node:fs\",\n \"node:fs/promises\",\n]);\nconst CHILD_PROCESS_MODULES: ReadonlySet<string> = new Set([\n \"child_process\",\n \"node:child_process\",\n]);\n// Calls that return a ChildProcess, whose stdin is a writable stream.\nconst CHILD_PROCESS_FACTORIES: ReadonlySet<string> = new Set([\n \"exec\",\n \"execFile\",\n \"fork\",\n \"spawn\",\n]);\nconst HTML_SANITIZER_LEAF_RE =\n /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;\nconst SANITIZER_RESULT_MEMBERS = new Set([\"value\", \"data\", \"html\"]);\nconst SANITIZER_POLICY = {\n allowlist: \"allowlist\",\n provenance: \"provenance\",\n} as const;\nconst SANITIZER_POLICY_VALUES = Object.values(SANITIZER_POLICY);\ntype SanitizerPolicy = (typeof SANITIZER_POLICY)[keyof typeof SANITIZER_POLICY];\n\nconst sanitizerPolicyFrom = (option: unknown): SanitizerPolicy => {\n if (option === undefined) {\n return SANITIZER_POLICY.allowlist;\n }\n if (typeof option !== \"object\" || option === null || Array.isArray(option)) {\n throw new Error(\"no-unsafe-inner-html options must be an object\");\n }\n const policy = Reflect.get(option, \"sanitizerPolicy\");\n switch (policy) {\n case undefined:\n case SANITIZER_POLICY.allowlist:\n return SANITIZER_POLICY.allowlist;\n case SANITIZER_POLICY.provenance:\n return SANITIZER_POLICY.provenance;\n default:\n throw new Error(`unknown sanitizer policy: ${String(policy)}`);\n }\n};\n\n// Use the same static member-name resolution for sinks and safe-value proofs.\nconst staticCalleeName = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (isIdentifier(expression)) return expression.name;\n if (expression?.type !== \"MemberExpression\") return null;\n const receiver = staticCalleeName(expression.object);\n const property = memberPropertyName(expression);\n return receiver === null || property === null\n ? null\n : `${receiver}.${property}`;\n};\n\nconst isSanitizerCall = (node: unknown): boolean => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return false;\n }\n const dotted = staticCalleeName(node.callee);\n if (dotted === null) {\n return false;\n }\n const parts = dotted.split(\".\");\n const leaf = parts.at(-1);\n if (leaf === undefined) {\n return false;\n }\n if (HTML_SANITIZER_LEAF_RE.test(leaf)) {\n return true;\n }\n return leaf === \"sanitize\" && /^dompurify$/iu.test(parts.at(-2) ?? \"\");\n};\n\nconst isSanitizerResult = (node: unknown): boolean => {\n const expression = unwrapExpression(node);\n if (expression?.type !== \"MemberExpression\") {\n return false;\n }\n const property = memberPropertyName(expression);\n return (\n property !== null &&\n SANITIZER_RESULT_MEMBERS.has(property) &&\n isSanitizerCall(expression.object)\n );\n};\n\ntype Located = { loc: { start: { line: number } } };\n\nconst hasLocation = (node: unknown): node is Located =>\n typeof node === \"object\" &&\n node !== null &&\n \"loc\" in node &&\n typeof node.loc === \"object\" &&\n node.loc !== null &&\n \"start\" in node.loc &&\n typeof node.loc.start === \"object\" &&\n node.loc.start !== null &&\n \"line\" in node.loc.start &&\n typeof node.loc.start.line === \"number\";\n\n// A node without location data never matches a waiver line.\nconst startLine = (node: unknown): number =>\n hasLocation(node) ? node.loc.start.line : Number.NaN;\n\ntype WaiverComment = {\n value: string;\n loc: { end: { line: number } };\n};\n\nconst isWaiverComment = (value: unknown): value is WaiverComment =>\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n typeof value.value === \"string\" &&\n WAIVER_RE.test(value.value) &&\n \"loc\" in value &&\n typeof value.loc === \"object\" &&\n value.loc !== null;\n\nconst jsxAttributeName = (node: unknown): string | null =>\n isAstNode(node) &&\n node.type === \"JSXIdentifier\" &&\n typeof node.name === \"string\"\n ? node.name\n : null;\n\n// A value is proven safe by its own static shape, independent of comments.\nconst isProvenSafeValue = (\n node: unknown,\n sanitizerPolicy: SanitizerPolicy,\n): boolean => {\n const expression = unwrapExpression(node);\n if (expression === null) {\n return false;\n }\n if (isStringLiteral(expression)) {\n return true;\n }\n if (\n sanitizerPolicy === SANITIZER_POLICY.allowlist &&\n (isSanitizerCall(expression) || isSanitizerResult(expression))\n ) {\n return true;\n }\n return (\n expression.type === \"TemplateLiteral\" &&\n Array.isArray(expression.expressions) &&\n expression.expressions.every((entry) =>\n isProvenSafeValue(entry, sanitizerPolicy),\n )\n );\n};\n\n// The static key of an object-literal Property: `a`, `\"a\"`, `[\"a\"]`.\nconst objectPropertyKey = (property: unknown): string | null => {\n if (!isAstNode(property)) {\n return null;\n }\n if (property.computed === true) {\n return staticStringValue(property.key);\n }\n return getPropertyName(property.key);\n};\n\nconst isOpaqueProperty = (property: AstNode): boolean =>\n property.type === \"SpreadElement\" ||\n (property.type === \"Property\" &&\n property.computed === true &&\n objectPropertyKey(property) === null);\n\nconst objectProperties = (node: unknown): AstNode[] => {\n const expression = unwrapExpression(node);\n if (\n expression?.type !== \"ObjectExpression\" ||\n !Array.isArray(expression.properties)\n ) {\n return [];\n }\n const properties: AstNode[] = [];\n for (const property of expression.properties.filter(isAstNode)) {\n if (\n property.type === \"SpreadElement\" &&\n unwrapExpression(property.argument)?.type === \"ObjectExpression\"\n ) {\n properties.push(...objectProperties(property.argument));\n continue;\n }\n properties.push(property);\n }\n return properties;\n};\n\ntype AppliedArguments = LiteralArgumentResolution;\n\nconst appliedArgumentsFor = (args: unknown[]): AppliedArguments => {\n const applied = unwrapExpression(args.at(1));\n if (\n applied === null ||\n (applied.type === \"Literal\" && applied.value === null) ||\n isIdentifier(applied, \"undefined\")\n ) {\n return { status: \"known\", args: [] };\n }\n if (applied.type !== \"ArrayExpression\" || !Array.isArray(applied.elements)) {\n return { status: \"opaque\", args: [], spread: args.at(1), tail: [] };\n }\n return resolveLiteralArraySpreads(applied.elements);\n};\n\n// The arguments the invoked function receives: `f.call(thisArg, ...args)`\n// shifts them by one; `f.apply(thisArg, [...])` forwards a literal array.\nconst invokedArguments = (call: unknown): AppliedArguments => {\n if (!isAstNode(call)) {\n return { status: \"known\", args: [] };\n }\n const rawArguments = Array.isArray(call.arguments) ? call.arguments : [];\n const resolved = resolveLiteralArraySpreads(rawArguments);\n const callee = unwrapExpression(call.callee);\n if (callee?.type !== \"MemberExpression\") return resolved;\n const method = memberPropertyName(callee);\n if (method === \"call\") {\n return { ...resolved, args: resolved.args.slice(1) };\n }\n if (method !== \"apply\") return resolved;\n if (resolved.status === \"opaque\") {\n if (resolved.args.length >= 2) return appliedArgumentsFor(resolved.args);\n return { ...resolved, args: [], tail: [] };\n }\n return appliedArgumentsFor(resolved.args);\n};\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"no-unsafe-inner-html\" },\n rules: {\n \"no-unsafe-inner-html\": {\n meta: {\n type: \"problem\",\n schema: [\n {\n type: \"object\",\n properties: {\n sanitizerPolicy: {\n type: \"string\",\n enum: SANITIZER_POLICY_VALUES,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n unsafeInnerHtml:\n \"Dynamic HTML injected into the DOM must be static, proven by \" +\n \"the configured sanitizer policy, or have an adjacent \" +\n \"`// safe-html: <provenance>` comment naming the trusted source. \" +\n \"Function names outside the allowlist are not proof because a \" +\n \"local identity function can shadow them.\",\n unsafeInnerHtmlSpread:\n \"Do not spread an object in dangerouslySetInnerHTML. Keep \" +\n \"`__html` inline so this rule can prove the HTML value is \" +\n \"sanitized or escaped.\",\n unsafeInnerHtmlComputedKey:\n \"Do not use an unresolved computed key in \" +\n \"dangerouslySetInnerHTML. Write an explicit `__html` property \" +\n \"so this rule can \" +\n \"prove the HTML value is sanitized or escaped.\",\n },\n },\n createOnce(context) {\n type BindingOptions = {\n node: unknown;\n scope: Scope;\n seen: Set<Variable>;\n };\n type BindingSource = {\n kind: \"expression\" | \"iteration\";\n node: AstNode | null;\n scope: Scope;\n };\n // All document, element and collection checks share one stable-binding path.\n const resolveStableBinding = ({\n node,\n scope,\n seen,\n }: BindingOptions): BindingSource | null => {\n let target = unwrapExpression(node);\n while (isIdentifier(target)) {\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(target.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length === 0) {\n return { kind: \"expression\", node: target, scope };\n }\n if (seen.has(variable)) return null;\n seen.add(variable);\n const definition = variable.defs.at(0);\n // A never-reassigned class or function declaration holds a\n // function, which is neither an element nor a document.\n if (\n variable.defs.length === 1 &&\n (definition?.type === \"ClassName\" ||\n definition?.type === \"FunctionName\") &&\n (definition.node.type === \"ClassDeclaration\" ||\n definition.node.type === \"FunctionDeclaration\") &&\n !variable.references.some((reference) => reference.isWrite())\n ) {\n return {\n kind: \"expression\",\n node: unwrapExpression(definition.node),\n scope,\n };\n }\n if (\n variable.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return null;\n }\n const declarator = definition.node;\n if (declarator.init !== null) {\n target = unwrapExpression(declarator.init);\n scope = context.sourceCode.getScope(declarator);\n continue;\n }\n const declaration = declarator.parent;\n if (declaration.type !== \"VariableDeclaration\") return null;\n const loop = declaration.parent;\n if (loop.type !== \"ForOfStatement\" || loop.left !== declaration) {\n return null;\n }\n return {\n kind: \"iteration\",\n node: unwrapExpression(loop.right),\n scope: context.sourceCode.getScope(loop.right),\n };\n }\n return { kind: \"expression\", node: target, scope };\n };\n const isKnownNonEmptyNamespace = (\n namespace: unknown,\n scope: Scope,\n ): boolean => {\n const source = resolveStableBinding({\n node: namespace,\n scope,\n seen: new Set<Variable>(),\n });\n if (source?.kind !== \"expression\") return false;\n const text = staticStringValue(source.node);\n // DOM normalizes the empty namespace to null. Unknowns stay checked.\n if (text !== null) return text !== \"\";\n if (source.node?.type !== \"Literal\") return false;\n const { value } = source.node;\n return (\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n );\n };\n\n const variableFor = (\n identifier: { name: string },\n initialScope: Scope,\n ): Variable | undefined => {\n let current: Scope | null = initialScope;\n while (current !== null) {\n const variable = current.set.get(identifier.name);\n if (variable !== undefined) return variable;\n current = current.upper;\n }\n return undefined;\n };\n const isKnownNonDocumentReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n const isUnshadowedGlobal = (\n identifier: unknown,\n name: string,\n ): boolean =>\n isIdentifier(identifier, name) &&\n variableFor(identifier, scope) === undefined;\n const isFileSystemImport = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n if (variable?.defs.length !== 1) return false;\n const definition = variable.defs.at(0);\n return (\n definition?.type === \"ImportBinding\" &&\n definition.parent?.type === \"ImportDeclaration\" &&\n FILE_SYSTEM_MODULES.has(definition.parent.source.value)\n );\n };\n // A const bound once to a child process factory call: a node\n // child_process import or the unshadowed Bun.spawn.\n const isChildProcess = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n const definition = variable?.defs.at(0);\n if (\n variable?.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\"\n )\n return false;\n const init = unwrapExpression(definition.node.init);\n if (init?.type !== \"CallExpression\") return false;\n const callee = unwrapExpression(init.callee);\n if (callee?.type === \"MemberExpression\")\n return (\n memberPropertyName(callee) === \"spawn\" &&\n isUnshadowedGlobal(unwrapExpression(callee.object), \"Bun\")\n );\n if (!isIdentifier(callee)) return false;\n const factory = variableFor(callee, scope);\n const binding = factory?.defs.at(0);\n return (\n factory?.defs.length === 1 &&\n binding?.type === \"ImportBinding\" &&\n binding.node.type === \"ImportSpecifier\" &&\n binding.node.imported.type === \"Identifier\" &&\n CHILD_PROCESS_FACTORIES.has(binding.node.imported.name) &&\n binding.parent?.type === \"ImportDeclaration\" &&\n CHILD_PROCESS_MODULES.has(binding.parent.source.value)\n );\n };\n const root = unwrapExpression(node);\n if (isUnshadowedGlobal(root, \"Bun\") || isFileSystemImport(root)) {\n return true;\n }\n if (root?.type !== \"MemberExpression\") return false;\n const stream = memberPropertyName(root);\n const owner = unwrapExpression(root.object);\n return (\n ((stream === \"stdout\" || stream === \"stderr\") &&\n isUnshadowedGlobal(owner, \"process\")) ||\n (stream === \"stdin\" && isChildProcess(owner))\n );\n };\n const isDocumentReceiver = (node: unknown, scope: Scope): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const source = resolveStableBinding({\n node,\n scope,\n seen: new Set<Variable>(),\n });\n if (source === null || source.kind !== \"expression\") return true;\n const receiver = source.node;\n if (isIdentifier(receiver, \"document\")) return true;\n if (receiver?.type !== \"MemberExpression\") return false;\n const name = memberPropertyName(receiver);\n return name !== null && DOCUMENT_MEMBERS.has(name);\n };\n\n const isDocumentSinkReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const receiver = unwrapExpression(node);\n if (!isIdentifier(receiver)) return true;\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(receiver.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length !== 1) return true;\n const definition = variable.defs.at(0);\n if (\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.init === null ||\n unwrapExpression(definition.node.init)?.type !==\n \"ObjectExpression\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\" ||\n variable.scope.variableScope !== scope.variableScope ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return true;\n }\n return false;\n };\n type DomTargetOptions = {\n node: unknown;\n scope: Scope;\n seen?: Set<Variable>;\n kind?: \"element\" | \"collection\";\n };\n const isKnownDomTarget = ({\n node,\n scope,\n seen = new Set<Variable>(),\n kind = \"element\",\n }: DomTargetOptions): boolean => {\n const source = resolveStableBinding({ node, scope, seen });\n // An unstable binding may have been reassigned to a DOM target. Fail\n // closed unless the resolver can prove a stable non-DOM value.\n if (source === null) return true;\n if (source.kind === \"iteration\") {\n return (\n kind === \"element\" &&\n isKnownDomTarget({\n node: source.node,\n scope: source.scope,\n seen,\n kind: \"collection\",\n })\n );\n }\n const target = source.node;\n scope = source.scope;\n const hasProducerReceiver = (\n receiver: unknown,\n producer: DomElementProducer,\n ): boolean => {\n if (\n producer.receiver !== \"element\" &&\n isDocumentReceiver(receiver, scope)\n ) {\n return true;\n }\n return (\n producer.receiver !== \"document\" &&\n isKnownDomTarget({ node: receiver, scope, seen })\n );\n };\n const isElementCollection = (collection: unknown): boolean =>\n isKnownDomTarget({\n node: collection,\n scope,\n seen,\n kind: \"collection\",\n });\n if (target?.type === \"MemberExpression\") {\n const property = memberPropertyName(target);\n const producer = (\n DOM_ELEMENT_PRODUCERS satisfies readonly DomElementProducer[]\n ).find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\"\n ? \"collection-property\"\n : \"property\") && entry.name === property,\n );\n if (producer !== undefined) {\n return hasProducerReceiver(target.object, producer);\n }\n const index = unwrapExpression(target.property);\n if (\n kind === \"element\" &&\n target.computed === true &&\n index?.type === \"Literal\" &&\n typeof index.value === \"number\" &&\n Number.isInteger(index.value) &&\n index.value >= 0\n ) {\n return isElementCollection(target.object);\n }\n }\n if (target?.type === \"CallExpression\") {\n const callee = invokedCallee(target);\n if (callee?.type !== \"MemberExpression\") return false;\n const method = memberPropertyName(callee);\n if (\n kind === \"element\" &&\n method === \"item\" &&\n isElementCollection(callee.object)\n ) {\n return true;\n }\n const producer = DOM_ELEMENT_PRODUCERS.find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\" ? \"collection\" : \"method\") &&\n entry.name === method,\n );\n return (\n producer !== undefined &&\n hasProducerReceiver(callee.object, producer)\n );\n }\n return kind === \"element\" && isDocumentReceiver(target, scope);\n };\n\n let sanitizerPolicy: SanitizerPolicy = SANITIZER_POLICY.allowlist;\n // Associate waivers after visiting the file, so source position decides\n // the first covered sink even when AST visitors inspect values out of order.\n const waiverLines = new Set<number>();\n type SinkObservation = {\n node: AstNode;\n kind:\n | \"safe\"\n | \"unsafe\"\n | \"opaque-payload-spread\"\n | \"opaque-payload-computed-key\";\n };\n const sinks: SinkObservation[] = [];\n const observeSink = (\n node: unknown,\n kind: SinkObservation[\"kind\"],\n ): void => {\n if (isAstNode(node)) sinks.push({ node, kind });\n };\n const messageIdFor = (kind: SinkObservation[\"kind\"]) => {\n if (kind === \"opaque-payload-spread\") {\n return \"unsafeInnerHtmlSpread\";\n }\n if (kind === \"opaque-payload-computed-key\") {\n return \"unsafeInnerHtmlComputedKey\";\n }\n return \"unsafeInnerHtml\";\n };\n\n const consumeWaiver = (node: unknown): boolean => {\n const line = startLine(node) - 1;\n if (!waiverLines.has(line)) {\n return false;\n }\n waiverLines.delete(line);\n return true;\n };\n\n const reportIfUnsafe = (node: unknown): void => {\n observeSink(\n node,\n isProvenSafeValue(node, sanitizerPolicy) ? \"safe\" : \"unsafe\",\n );\n };\n\n // The value of a `dangerouslySetInnerHTML` prop: an inline object has\n // its `__html` checked and its spreads rejected; anything else is a\n // hoisted payload whose HTML this rule cannot see.\n const checkDangerousPayload = (\n value: unknown,\n htmlKey: string,\n ): void => {\n const expression = unwrapExpression(value);\n if (expression?.type !== \"ObjectExpression\") {\n reportIfUnsafe(value);\n return;\n }\n for (const property of objectProperties(expression)) {\n if (isOpaqueProperty(property)) {\n observeSink(\n property,\n property.type === \"SpreadElement\"\n ? \"opaque-payload-spread\"\n : \"opaque-payload-computed-key\",\n );\n continue;\n }\n if (\n property.type === \"Property\" &&\n objectPropertyKey(property) === htmlKey\n ) {\n reportIfUnsafe(property.value);\n }\n }\n };\n\n const checkReactHtmlProp = (\n prop: ReactHtmlProp,\n value: unknown,\n ): void => {\n switch (prop.kind) {\n case \"payload\":\n checkDangerousPayload(value, prop.htmlKey);\n return;\n case \"value\":\n reportIfUnsafe(value);\n return;\n default: {\n const exhaustive: never = prop;\n throw new Error(`Unknown React HTML prop: ${String(exhaustive)}`);\n }\n }\n };\n\n const checkReactProps = (value: unknown): void => {\n for (const property of objectProperties(value)) {\n if (property.type !== \"Property\") continue;\n const key = objectPropertyKey(property);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n // Payload properties are checked once by the Property visitor.\n if (prop?.kind === \"value\")\n checkReactHtmlProp(prop, property.value);\n }\n };\n\n const checkHtmlPropertySources = (\n sources: unknown[],\n domTarget: boolean,\n ): void => {\n for (const source of sources) {\n const expression = unwrapExpression(source);\n // Each branch of a conditional or logical expression may be the\n // value Object.assign copies from; the condition itself is not,\n // and the left side of `&&` is copied only when falsy, so never\n // as an object.\n if (expression?.type === \"ConditionalExpression\") {\n checkHtmlPropertySources(\n [expression.consequent, expression.alternate],\n domTarget,\n );\n continue;\n }\n // Object.assign skips null and undefined sources.\n if (\n (expression?.type === \"Literal\" && expression.value === null) ||\n isIdentifier(expression, \"undefined\")\n ) {\n continue;\n }\n if (expression?.type === \"LogicalExpression\") {\n checkHtmlPropertySources(\n expression.operator === \"&&\"\n ? [expression.right]\n : [expression.left, expression.right],\n domTarget,\n );\n continue;\n }\n if (expression?.type !== \"ObjectExpression\") {\n if (domTarget) {\n reportIfUnsafe(source);\n }\n continue;\n }\n for (const property of objectProperties(source)) {\n if (isOpaqueProperty(property)) {\n if (domTarget) {\n reportIfUnsafe(property);\n }\n continue;\n }\n const key =\n property.type === \"Property\"\n ? objectPropertyKey(property)\n : null;\n if (key !== null && HTML_PROPERTIES.has(key)) {\n reportIfUnsafe(property.value);\n }\n }\n }\n };\n\n return {\n before() {\n sanitizerPolicy = sanitizerPolicyFrom(context.options.at(0));\n waiverLines.clear();\n sinks.length = 0;\n },\n \"Program:exit\"() {\n sinks.sort(\n (left, right) => left.node.range[0] - right.node.range[0],\n );\n for (const { node, kind } of sinks) {\n const waived = consumeWaiver(node);\n if (kind === \"safe\" || (kind === \"unsafe\" && waived)) continue;\n context.report({\n node,\n messageId: messageIdFor(kind),\n });\n }\n sinks.length = 0;\n },\n Program(node) {\n const comments: unknown[] = Array.isArray(node.comments)\n ? node.comments\n : [];\n for (const comment of comments) {\n if (isWaiverComment(comment)) {\n waiverLines.add(comment.loc.end.line);\n }\n }\n },\n\n JSXAttribute(node) {\n const name = jsxAttributeName(node.name);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === name);\n if (prop === undefined) return;\n const value = unwrapExpression(node.value);\n checkReactHtmlProp(\n prop,\n value?.type === \"JSXExpressionContainer\"\n ? value.expression\n : value,\n );\n },\n\n JSXSpreadAttribute(node) {\n checkReactProps(node.argument);\n },\n\n // Payload objects remain checked when hoisted or spread into JSX.\n // Direct value props use the JSX and factory props contexts.\n Property(node) {\n const key = objectPropertyKey(node);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n if (prop?.kind !== \"payload\") return;\n const container = node.parent;\n if (isAstNode(container) && container.type === \"ObjectExpression\") {\n checkReactHtmlProp(prop, node.value);\n }\n },\n\n AssignmentExpression(node) {\n const target = unwrapExpression(node.left);\n if (target?.type !== \"MemberExpression\") {\n return;\n }\n const name = memberPropertyName(target);\n if (name !== null && HTML_PROPERTIES.has(name)) {\n reportIfUnsafe(node.right);\n }\n },\n\n CallExpression(node) {\n const call: unknown = node;\n const callee = isAstNode(call) ? invokedCallee(call) : null;\n const invoked = invokedArguments(node);\n const factory = staticCalleeName(callee);\n if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {\n checkReactProps(invoked.args.at(1));\n return;\n }\n if (callee?.type !== \"MemberExpression\") {\n return;\n }\n const method = memberPropertyName(callee);\n if (method === null) {\n return;\n }\n if (HTML_FIRST_ARGUMENT_METHODS.has(method)) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(0));\n return;\n }\n if (method === \"insertAdjacentHTML\") {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(1));\n return;\n }\n const attributeSink = HTML_ATTRIBUTE_SINKS.find(\n (entry) => entry.name === method,\n );\n if (attributeSink !== undefined) {\n if (\n attributeSink.namespaceIndex !== null &&\n isKnownNonEmptyNamespace(\n invoked.args.at(attributeSink.namespaceIndex),\n context.sourceCode.getScope(node),\n )\n ) {\n return;\n }\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n const attribute = staticStringValue(\n invoked.args.at(attributeSink.nameIndex),\n );\n if (attribute?.toLowerCase() === \"srcdoc\") {\n reportIfUnsafe(invoked.args.at(attributeSink.valueIndex));\n }\n return;\n }\n if (\n DOCUMENT_WRITE_METHODS.has(method) &&\n isDocumentSinkReceiver(\n callee.object,\n context.sourceCode.getScope(node),\n )\n ) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n observeSink(\n node,\n invoked.args.every((argument) =>\n isProvenSafeValue(argument, sanitizerPolicy),\n )\n ? \"safe\"\n : \"unsafe\",\n );\n return;\n }\n if (isIdentifier(callee.object, \"Object\") && method === \"assign\") {\n const domTarget = isKnownDomTarget({\n node: invoked.args.at(0),\n scope: context.sourceCode.getScope(node),\n });\n checkHtmlPropertySources(\n [\n ...invoked.args.slice(1),\n ...(invoked.status === \"opaque\" ? invoked.tail : []),\n ],\n domTarget,\n );\n if (invoked.status === \"opaque\" && domTarget) {\n reportIfUnsafe(invoked.spread);\n }\n }\n },\n };\n },\n },\n },\n});\n\nexport default plugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"no-unsafe-inner-html.cjs","names":["unwrapExpression","isIdentifier","memberPropertyName","isAstNode","isStringLiteral","staticStringValue","getPropertyName","resolveLiteralArraySpreads","eslintCompatPlugin","invokedCallee","exhaustive"],"sources":["../src/no-unsafe-inner-html.ts"],"sourcesContent":["// Forbid injecting un-proven HTML into the DOM.\n//\n// Raw HTML may only reach the DOM from a value that is provably sanitized or\n// escaped. This rule covers common DOM and JSX sinks while allowing explicit\n// sanitizer calls and narrowly scoped provenance comments.\n//\n// Sinks, all sharing one allowlist:\n// • `__html` of a `dangerouslySetInnerHTML` object, whether the object is a\n// JSX attribute value or a property of any object literal (props passed\n// to `createElement` / `jsx()`, or spread into JSX).\n// • assignment to `innerHTML`, `outerHTML` or `srcdoc`, dot or static\n// bracket notation, and the same keys in an `Object.assign` source\n// (either branch of a conditional or logical source).\n// • the JSX `srcDoc` attribute.\n// • `insertAdjacentHTML`, `setHTMLUnsafe`, `createContextualFragment`,\n// `setAttribute(\"srcdoc\", …)` / `setAttributeNS(…, \"srcdoc\", …)`,\n// and `write` / `writeln` on a document.\n//\n// In the default `allowlist` policy, a value is allowed when it is:\n// • a static string Literal or TemplateLiteral whose interpolations are all\n// independently static, OR\n// • passed through the shared HTML sanitizer allowlist (`escapeHtml`,\n// `escapeXml`, `sanitizeHtml`, `sanitizeDom`, `DOMPurify.sanitize`, and\n// related established names), including `.value` / `.data` / `.html`\n// result accessors; OR\n// • annotated by a `// safe-html: <provenance>` comment with non-empty text\n// on the line directly above the sink. One comment covers one sink: a\n// second sink starting on the same line needs its own annotation.\n// The `provenance` policy omits the sanitizer-name allowlist and requires the\n// comment for dynamic sanitizer results as well.\n//\n// The sanitizer allowlist preserves the shared plugin contract. For dynamic\n// values outside that list, function names are not proof: a local identity\n// function can be named `sanitizeHtml`. Add explicit provenance at the sink\n// until the codebase has a branded SafeHtml boundary.\n\nimport {\n eslintCompatPlugin,\n type Plugin,\n type Scope,\n type Variable,\n} from \"@oxlint/plugins\";\n\nimport type { AstNode, LiteralArgumentResolution } from \"./utils.ts\";\nimport {\n getPropertyName,\n invokedCallee,\n isAstNode,\n isIdentifier,\n isStringLiteral,\n memberPropertyName,\n resolveLiteralArraySpreads,\n staticStringValue,\n unwrapExpression,\n} from \"./utils.ts\";\n\ntype DomElementProducer = {\n name: string;\n kind: \"property\" | \"method\" | \"collection\" | \"collection-property\";\n receiver: \"document\" | \"element\" | \"either\";\n};\n\n// DOM element provenance and the generated equivalence corpus share this table.\nexport const DOM_ELEMENT_PRODUCERS = [\n { name: \"body\", kind: \"property\", receiver: \"document\" },\n { name: \"head\", kind: \"property\", receiver: \"document\" },\n { name: \"documentElement\", kind: \"property\", receiver: \"document\" },\n { name: \"activeElement\", kind: \"property\", receiver: \"document\" },\n { name: \"scrollingElement\", kind: \"property\", receiver: \"document\" },\n { name: \"fullscreenElement\", kind: \"property\", receiver: \"document\" },\n { name: \"createElement\", kind: \"method\", receiver: \"document\" },\n { name: \"createElementNS\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementById\", kind: \"method\", receiver: \"document\" },\n { name: \"querySelector\", kind: \"method\", receiver: \"either\" },\n { name: \"closest\", kind: \"method\", receiver: \"element\" },\n { name: \"elementFromPoint\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementsByClassName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagNameNS\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByName\", kind: \"collection\", receiver: \"document\" },\n { name: \"querySelectorAll\", kind: \"collection\", receiver: \"either\" },\n { name: \"children\", kind: \"collection-property\", receiver: \"element\" },\n { name: \"parentElement\", kind: \"property\", receiver: \"element\" },\n { name: \"firstElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"lastElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"nextElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"previousElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"offsetParent\", kind: \"property\", receiver: \"element\" },\n] as const;\n\n// The provenance text after the marker must be non-empty.\nconst WAIVER_RE = /^\\s*safe-html:\\s*\\S/u;\n\nexport const REACT_HTML_PROPS = [\n { name: \"dangerouslySetInnerHTML\", kind: \"payload\", htmlKey: \"__html\" },\n { name: \"srcDoc\", kind: \"value\" },\n { name: \"srcdoc\", kind: \"value\" },\n] as const;\ntype ReactHtmlProp = (typeof REACT_HTML_PROPS)[number];\nexport const REACT_ELEMENT_FACTORIES: ReadonlySet<string> = new Set([\n \"createElement\",\n \"React.createElement\",\n \"jsx\",\n \"jsxs\",\n \"jsxDEV\",\n]);\nconst HTML_PROPERTIES: ReadonlySet<string> = new Set([\n \"innerHTML\",\n \"outerHTML\",\n \"srcdoc\",\n]);\n// Methods whose first argument is parsed as HTML.\nconst HTML_FIRST_ARGUMENT_METHODS: ReadonlySet<string> = new Set([\n \"createContextualFragment\",\n \"setHTMLUnsafe\",\n]);\nexport const DOCUMENT_WRITE_METHODS: ReadonlySet<string> = new Set([\n \"write\",\n \"writeln\",\n]);\nexport const HTML_ATTRIBUTE_SINKS = [\n { name: \"setAttribute\", namespaceIndex: null, nameIndex: 0, valueIndex: 1 },\n { name: \"setAttributeNS\", namespaceIndex: 0, nameIndex: 1, valueIndex: 2 },\n] as const;\nconst DOCUMENT_MEMBERS: ReadonlySet<string> = new Set([\n \"contentDocument\",\n \"document\",\n \"ownerDocument\",\n]);\nconst FILE_SYSTEM_MODULES: ReadonlySet<string> = new Set([\n \"fs\",\n \"fs/promises\",\n \"node:fs\",\n \"node:fs/promises\",\n]);\nconst CHILD_PROCESS_MODULES: ReadonlySet<string> = new Set([\n \"child_process\",\n \"node:child_process\",\n]);\n// Calls that return a ChildProcess, whose stdin is a writable stream.\nconst CHILD_PROCESS_FACTORIES: ReadonlySet<string> = new Set([\n \"exec\",\n \"execFile\",\n \"fork\",\n \"spawn\",\n]);\nconst HTML_SANITIZER_LEAF_RE =\n /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;\nconst SANITIZER_RESULT_MEMBERS = new Set([\"value\", \"data\", \"html\"]);\nconst SANITIZER_POLICY = {\n allowlist: \"allowlist\",\n provenance: \"provenance\",\n} as const;\nconst SANITIZER_POLICY_VALUES = Object.values(SANITIZER_POLICY);\ntype SanitizerPolicy = (typeof SANITIZER_POLICY)[keyof typeof SANITIZER_POLICY];\n\nconst sanitizerPolicyFrom = (option: unknown): SanitizerPolicy => {\n if (option === undefined) {\n return SANITIZER_POLICY.allowlist;\n }\n if (typeof option !== \"object\" || option === null || Array.isArray(option)) {\n throw new Error(\"no-unsafe-inner-html options must be an object\");\n }\n const policy = Reflect.get(option, \"sanitizerPolicy\");\n switch (policy) {\n case undefined:\n case SANITIZER_POLICY.allowlist:\n return SANITIZER_POLICY.allowlist;\n case SANITIZER_POLICY.provenance:\n return SANITIZER_POLICY.provenance;\n default:\n throw new Error(`unknown sanitizer policy: ${String(policy)}`);\n }\n};\n\n// Use the same static member-name resolution for sinks and safe-value proofs.\nconst staticCalleeName = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (isIdentifier(expression)) return expression.name;\n if (expression?.type !== \"MemberExpression\") return null;\n const receiver = staticCalleeName(expression.object);\n const property = memberPropertyName(expression);\n return receiver === null || property === null\n ? null\n : `${receiver}.${property}`;\n};\n\nconst isSanitizerCall = (node: unknown): boolean => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return false;\n }\n const dotted = staticCalleeName(node.callee);\n if (dotted === null) {\n return false;\n }\n const parts = dotted.split(\".\");\n const leaf = parts.at(-1);\n if (leaf === undefined) {\n return false;\n }\n if (HTML_SANITIZER_LEAF_RE.test(leaf)) {\n return true;\n }\n return leaf === \"sanitize\" && /^dompurify$/iu.test(parts.at(-2) ?? \"\");\n};\n\nconst isSanitizerResult = (node: unknown): boolean => {\n const expression = unwrapExpression(node);\n if (expression?.type !== \"MemberExpression\") {\n return false;\n }\n const property = memberPropertyName(expression);\n return (\n property !== null &&\n SANITIZER_RESULT_MEMBERS.has(property) &&\n isSanitizerCall(expression.object)\n );\n};\n\ntype Located = { loc: { start: { line: number } } };\n\nconst hasLocation = (node: unknown): node is Located =>\n typeof node === \"object\" &&\n node !== null &&\n \"loc\" in node &&\n typeof node.loc === \"object\" &&\n node.loc !== null &&\n \"start\" in node.loc &&\n typeof node.loc.start === \"object\" &&\n node.loc.start !== null &&\n \"line\" in node.loc.start &&\n typeof node.loc.start.line === \"number\";\n\n// A node without location data never matches a waiver line.\nconst startLine = (node: unknown): number =>\n hasLocation(node) ? node.loc.start.line : Number.NaN;\n\ntype WaiverComment = {\n value: string;\n loc: { end: { line: number } };\n};\n\nconst isWaiverComment = (value: unknown): value is WaiverComment =>\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n typeof value.value === \"string\" &&\n WAIVER_RE.test(value.value) &&\n \"loc\" in value &&\n typeof value.loc === \"object\" &&\n value.loc !== null;\n\nconst jsxAttributeName = (node: unknown): string | null =>\n isAstNode(node) &&\n node.type === \"JSXIdentifier\" &&\n typeof node.name === \"string\"\n ? node.name\n : null;\n\n// A value is proven safe by its own static shape, independent of comments.\nconst isProvenSafeValue = (\n node: unknown,\n sanitizerPolicy: SanitizerPolicy,\n): boolean => {\n const expression = unwrapExpression(node);\n if (expression === null) {\n return false;\n }\n if (isStringLiteral(expression)) {\n return true;\n }\n if (\n sanitizerPolicy === SANITIZER_POLICY.allowlist &&\n (isSanitizerCall(expression) || isSanitizerResult(expression))\n ) {\n return true;\n }\n return (\n expression.type === \"TemplateLiteral\" &&\n Array.isArray(expression.expressions) &&\n expression.expressions.every((entry) =>\n isProvenSafeValue(entry, sanitizerPolicy),\n )\n );\n};\n\n// The static key of an object-literal Property: `a`, `\"a\"`, `[\"a\"]`.\nconst objectPropertyKey = (property: unknown): string | null => {\n if (!isAstNode(property)) {\n return null;\n }\n if (property.computed === true) {\n return staticStringValue(property.key);\n }\n return getPropertyName(property.key);\n};\n\nconst isOpaqueProperty = (property: AstNode): boolean =>\n property.type === \"SpreadElement\" ||\n (property.type === \"Property\" &&\n property.computed === true &&\n objectPropertyKey(property) === null);\n\nconst objectProperties = (node: unknown): AstNode[] => {\n const expression = unwrapExpression(node);\n if (\n expression?.type !== \"ObjectExpression\" ||\n !Array.isArray(expression.properties)\n ) {\n return [];\n }\n const properties: AstNode[] = [];\n for (const property of expression.properties.filter(isAstNode)) {\n if (\n property.type === \"SpreadElement\" &&\n unwrapExpression(property.argument)?.type === \"ObjectExpression\"\n ) {\n properties.push(...objectProperties(property.argument));\n continue;\n }\n properties.push(property);\n }\n return properties;\n};\n\ntype AppliedArguments = LiteralArgumentResolution;\n\nconst appliedArgumentsFor = (args: unknown[]): AppliedArguments => {\n const applied = unwrapExpression(args.at(1));\n if (\n applied === null ||\n (applied.type === \"Literal\" && applied.value === null) ||\n isIdentifier(applied, \"undefined\")\n ) {\n return { status: \"known\", args: [] };\n }\n if (applied.type !== \"ArrayExpression\" || !Array.isArray(applied.elements)) {\n return { status: \"opaque\", args: [], spread: args.at(1), tail: [] };\n }\n return resolveLiteralArraySpreads(applied.elements);\n};\n\n// The arguments the invoked function receives: `f.call(thisArg, ...args)`\n// shifts them by one; `f.apply(thisArg, [...])` forwards a literal array.\nconst invokedArguments = (call: unknown): AppliedArguments => {\n if (!isAstNode(call)) {\n return { status: \"known\", args: [] };\n }\n const rawArguments = Array.isArray(call.arguments) ? call.arguments : [];\n const resolved = resolveLiteralArraySpreads(rawArguments);\n const callee = unwrapExpression(call.callee);\n if (callee?.type !== \"MemberExpression\") return resolved;\n const method = memberPropertyName(callee);\n if (method === \"call\") {\n return { ...resolved, args: resolved.args.slice(1) };\n }\n if (method !== \"apply\") return resolved;\n if (resolved.status === \"opaque\") {\n if (resolved.args.length >= 2) return appliedArgumentsFor(resolved.args);\n return { ...resolved, args: [], tail: [] };\n }\n return appliedArgumentsFor(resolved.args);\n};\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"no-unsafe-inner-html\" },\n rules: {\n \"no-unsafe-inner-html\": {\n meta: {\n type: \"problem\",\n schema: [\n {\n type: \"object\",\n properties: {\n sanitizerPolicy: {\n type: \"string\",\n enum: SANITIZER_POLICY_VALUES,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n unsafeInnerHtml:\n \"Dynamic HTML injected into the DOM must be static, proven by \" +\n \"the configured sanitizer policy, or have an adjacent \" +\n \"`// safe-html: <provenance>` comment naming the trusted source. \" +\n \"Function names outside the allowlist are not proof because a \" +\n \"local identity function can shadow them.\",\n unsafeInnerHtmlSpread:\n \"Do not spread an object in dangerouslySetInnerHTML. Keep \" +\n \"`__html` inline so this rule can prove the HTML value is \" +\n \"sanitized or escaped.\",\n unsafeInnerHtmlComputedKey:\n \"Do not use an unresolved computed key in \" +\n \"dangerouslySetInnerHTML. Write an explicit `__html` property \" +\n \"so this rule can \" +\n \"prove the HTML value is sanitized or escaped.\",\n },\n },\n createOnce(context) {\n type BindingOptions = {\n node: unknown;\n scope: Scope;\n seen: Set<Variable>;\n };\n type BindingSource = {\n kind: \"expression\" | \"iteration\";\n node: AstNode | null;\n scope: Scope;\n };\n // All document, element and collection checks share one stable-binding path.\n const resolveStableBinding = ({\n node,\n scope,\n seen,\n }: BindingOptions): BindingSource | null => {\n let target = unwrapExpression(node);\n while (isIdentifier(target)) {\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(target.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length === 0) {\n return { kind: \"expression\", node: target, scope };\n }\n if (seen.has(variable)) return null;\n seen.add(variable);\n const definition = variable.defs.at(0);\n // A never-reassigned class or function declaration holds a\n // function, which is neither an element nor a document.\n if (\n variable.defs.length === 1 &&\n (definition?.type === \"ClassName\" ||\n definition?.type === \"FunctionName\") &&\n (definition.node.type === \"ClassDeclaration\" ||\n definition.node.type === \"FunctionDeclaration\") &&\n !variable.references.some((reference) => reference.isWrite())\n ) {\n return {\n kind: \"expression\",\n node: unwrapExpression(definition.node),\n scope,\n };\n }\n if (\n variable.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return null;\n }\n const declarator = definition.node;\n if (declarator.init !== null) {\n target = unwrapExpression(declarator.init);\n scope = context.sourceCode.getScope(declarator);\n continue;\n }\n const declaration = declarator.parent;\n if (declaration.type !== \"VariableDeclaration\") return null;\n const loop = declaration.parent;\n if (loop.type !== \"ForOfStatement\" || loop.left !== declaration) {\n return null;\n }\n return {\n kind: \"iteration\",\n node: unwrapExpression(loop.right),\n scope: context.sourceCode.getScope(loop.right),\n };\n }\n return { kind: \"expression\", node: target, scope };\n };\n const isKnownNonEmptyNamespace = (\n namespace: unknown,\n scope: Scope,\n ): boolean => {\n const source = resolveStableBinding({\n node: namespace,\n scope,\n seen: new Set<Variable>(),\n });\n if (source?.kind !== \"expression\") return false;\n const text = staticStringValue(source.node);\n // DOM normalizes the empty namespace to null. Unknowns stay checked.\n if (text !== null) return text !== \"\";\n if (source.node?.type !== \"Literal\") return false;\n const { value } = source.node;\n return (\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n );\n };\n\n const variableFor = (\n identifier: { name: string },\n initialScope: Scope,\n ): Variable | undefined => {\n let current: Scope | null = initialScope;\n while (current !== null) {\n const variable = current.set.get(identifier.name);\n if (variable !== undefined) return variable;\n current = current.upper;\n }\n return undefined;\n };\n const isKnownNonDocumentReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n const isUnshadowedGlobal = (\n identifier: unknown,\n name: string,\n ): boolean =>\n isIdentifier(identifier, name) &&\n variableFor(identifier, scope) === undefined;\n const isFileSystemImport = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n if (variable?.defs.length !== 1) return false;\n const definition = variable.defs.at(0);\n return (\n definition?.type === \"ImportBinding\" &&\n definition.parent?.type === \"ImportDeclaration\" &&\n FILE_SYSTEM_MODULES.has(definition.parent.source.value)\n );\n };\n // A const bound once to a child process factory call: a node\n // child_process import or the unshadowed Bun.spawn.\n const isChildProcess = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n const definition = variable?.defs.at(0);\n if (\n variable?.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\"\n )\n return false;\n const init = unwrapExpression(definition.node.init);\n if (init?.type !== \"CallExpression\") return false;\n const callee = unwrapExpression(init.callee);\n if (callee?.type === \"MemberExpression\")\n return (\n memberPropertyName(callee) === \"spawn\" &&\n isUnshadowedGlobal(unwrapExpression(callee.object), \"Bun\")\n );\n if (!isIdentifier(callee)) return false;\n const factory = variableFor(callee, scope);\n const binding = factory?.defs.at(0);\n return (\n factory?.defs.length === 1 &&\n binding?.type === \"ImportBinding\" &&\n binding.node.type === \"ImportSpecifier\" &&\n binding.node.imported.type === \"Identifier\" &&\n CHILD_PROCESS_FACTORIES.has(binding.node.imported.name) &&\n binding.parent?.type === \"ImportDeclaration\" &&\n CHILD_PROCESS_MODULES.has(binding.parent.source.value)\n );\n };\n const root = unwrapExpression(node);\n if (isUnshadowedGlobal(root, \"Bun\") || isFileSystemImport(root)) {\n return true;\n }\n if (root?.type !== \"MemberExpression\") return false;\n const stream = memberPropertyName(root);\n const owner = unwrapExpression(root.object);\n return (\n ((stream === \"stdout\" || stream === \"stderr\") &&\n isUnshadowedGlobal(owner, \"process\")) ||\n (stream === \"stdin\" && isChildProcess(owner))\n );\n };\n const isDocumentReceiver = (node: unknown, scope: Scope): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const source = resolveStableBinding({\n node,\n scope,\n seen: new Set<Variable>(),\n });\n if (source === null || source.kind !== \"expression\") return true;\n const receiver = source.node;\n if (isIdentifier(receiver, \"document\")) return true;\n if (receiver?.type !== \"MemberExpression\") return false;\n const name = memberPropertyName(receiver);\n return name !== null && DOCUMENT_MEMBERS.has(name);\n };\n\n const isDocumentSinkReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const receiver = unwrapExpression(node);\n if (!isIdentifier(receiver)) return true;\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(receiver.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length !== 1) return true;\n const definition = variable.defs.at(0);\n if (\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.init === null ||\n unwrapExpression(definition.node.init)?.type !==\n \"ObjectExpression\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\" ||\n variable.scope.variableScope !== scope.variableScope ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return true;\n }\n return false;\n };\n type DomTargetOptions = {\n node: unknown;\n scope: Scope;\n seen?: Set<Variable>;\n kind?: \"element\" | \"collection\";\n };\n const isKnownDomTarget = ({\n node,\n scope,\n seen = new Set<Variable>(),\n kind = \"element\",\n }: DomTargetOptions): boolean => {\n const source = resolveStableBinding({ node, scope, seen });\n // An unstable binding may have been reassigned to a DOM target. Fail\n // closed unless the resolver can prove a stable non-DOM value.\n if (source === null) return true;\n if (source.kind === \"iteration\") {\n return (\n kind === \"element\" &&\n isKnownDomTarget({\n node: source.node,\n scope: source.scope,\n seen,\n kind: \"collection\",\n })\n );\n }\n const target = source.node;\n scope = source.scope;\n const hasProducerReceiver = (\n receiver: unknown,\n producer: DomElementProducer,\n ): boolean => {\n if (\n producer.receiver !== \"element\" &&\n isDocumentReceiver(receiver, scope)\n ) {\n return true;\n }\n return (\n producer.receiver !== \"document\" &&\n isKnownDomTarget({ node: receiver, scope, seen })\n );\n };\n const isElementCollection = (collection: unknown): boolean =>\n isKnownDomTarget({\n node: collection,\n scope,\n seen,\n kind: \"collection\",\n });\n if (target?.type === \"MemberExpression\") {\n const property = memberPropertyName(target);\n const producer = (\n DOM_ELEMENT_PRODUCERS satisfies readonly DomElementProducer[]\n ).find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\"\n ? \"collection-property\"\n : \"property\") && entry.name === property,\n );\n if (producer !== undefined) {\n return hasProducerReceiver(target.object, producer);\n }\n const index = unwrapExpression(target.property);\n if (\n kind === \"element\" &&\n target.computed === true &&\n index?.type === \"Literal\" &&\n typeof index.value === \"number\" &&\n Number.isInteger(index.value) &&\n index.value >= 0\n ) {\n return isElementCollection(target.object);\n }\n }\n if (target?.type === \"CallExpression\") {\n const callee = invokedCallee(target);\n if (callee?.type !== \"MemberExpression\") return false;\n const method = memberPropertyName(callee);\n if (\n kind === \"element\" &&\n method === \"item\" &&\n isElementCollection(callee.object)\n ) {\n return true;\n }\n const producer = DOM_ELEMENT_PRODUCERS.find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\" ? \"collection\" : \"method\") &&\n entry.name === method,\n );\n return (\n producer !== undefined &&\n hasProducerReceiver(callee.object, producer)\n );\n }\n return kind === \"element\" && isDocumentReceiver(target, scope);\n };\n\n let sanitizerPolicy: SanitizerPolicy = SANITIZER_POLICY.allowlist;\n // Associate waivers after visiting the file, so source position decides\n // the first covered sink even when AST visitors inspect values out of order.\n const waiverLines = new Set<number>();\n type SinkObservation = {\n node: AstNode;\n kind:\n | \"safe\"\n | \"unsafe\"\n | \"opaque-payload-spread\"\n | \"opaque-payload-computed-key\";\n };\n const sinks: SinkObservation[] = [];\n const observeSink = (\n node: unknown,\n kind: SinkObservation[\"kind\"],\n ): void => {\n if (isAstNode(node)) sinks.push({ node, kind });\n };\n const messageIdFor = (kind: SinkObservation[\"kind\"]) => {\n if (kind === \"opaque-payload-spread\") {\n return \"unsafeInnerHtmlSpread\";\n }\n if (kind === \"opaque-payload-computed-key\") {\n return \"unsafeInnerHtmlComputedKey\";\n }\n return \"unsafeInnerHtml\";\n };\n\n const consumeWaiver = (node: unknown): boolean => {\n const line = startLine(node) - 1;\n if (!waiverLines.has(line)) {\n return false;\n }\n waiverLines.delete(line);\n return true;\n };\n\n const reportIfUnsafe = (node: unknown): void => {\n observeSink(\n node,\n isProvenSafeValue(node, sanitizerPolicy) ? \"safe\" : \"unsafe\",\n );\n };\n\n // The value of a `dangerouslySetInnerHTML` prop: an inline object has\n // its `__html` checked and its spreads rejected; anything else is a\n // hoisted payload whose HTML this rule cannot see.\n const checkDangerousPayload = (\n value: unknown,\n htmlKey: string,\n ): void => {\n const expression = unwrapExpression(value);\n if (expression?.type !== \"ObjectExpression\") {\n reportIfUnsafe(value);\n return;\n }\n for (const property of objectProperties(expression)) {\n if (isOpaqueProperty(property)) {\n observeSink(\n property,\n property.type === \"SpreadElement\"\n ? \"opaque-payload-spread\"\n : \"opaque-payload-computed-key\",\n );\n continue;\n }\n if (\n property.type === \"Property\" &&\n objectPropertyKey(property) === htmlKey\n ) {\n reportIfUnsafe(property.value);\n }\n }\n };\n\n const checkReactHtmlProp = (\n prop: ReactHtmlProp,\n value: unknown,\n ): void => {\n switch (prop.kind) {\n case \"payload\":\n checkDangerousPayload(value, prop.htmlKey);\n return;\n case \"value\":\n reportIfUnsafe(value);\n return;\n default: {\n const exhaustive: never = prop;\n throw new Error(`Unknown React HTML prop: ${String(exhaustive)}`);\n }\n }\n };\n\n const checkReactProps = (value: unknown): void => {\n for (const property of objectProperties(value)) {\n if (property.type !== \"Property\") continue;\n const key = objectPropertyKey(property);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n // Payload properties are checked once by the Property visitor.\n if (prop?.kind === \"value\")\n checkReactHtmlProp(prop, property.value);\n }\n };\n\n const checkHtmlPropertySources = (\n sources: unknown[],\n domTarget: boolean,\n ): void => {\n for (const source of sources) {\n const expression = unwrapExpression(source);\n // Each branch of a conditional or logical expression may be the\n // value Object.assign copies from; the condition itself is not,\n // and the left side of `&&` is copied only when falsy, so never\n // as an object.\n if (expression?.type === \"ConditionalExpression\") {\n checkHtmlPropertySources(\n [expression.consequent, expression.alternate],\n domTarget,\n );\n continue;\n }\n // Object.assign skips null and undefined sources.\n if (\n (expression?.type === \"Literal\" && expression.value === null) ||\n isIdentifier(expression, \"undefined\")\n ) {\n continue;\n }\n if (expression?.type === \"LogicalExpression\") {\n checkHtmlPropertySources(\n expression.operator === \"&&\"\n ? [expression.right]\n : [expression.left, expression.right],\n domTarget,\n );\n continue;\n }\n if (expression?.type !== \"ObjectExpression\") {\n if (domTarget) {\n reportIfUnsafe(source);\n }\n continue;\n }\n for (const property of objectProperties(source)) {\n if (isOpaqueProperty(property)) {\n if (domTarget) {\n reportIfUnsafe(property);\n }\n continue;\n }\n const key =\n property.type === \"Property\"\n ? objectPropertyKey(property)\n : null;\n if (key !== null && HTML_PROPERTIES.has(key)) {\n reportIfUnsafe(property.value);\n }\n }\n }\n };\n\n return {\n before() {\n sanitizerPolicy = sanitizerPolicyFrom(context.options.at(0));\n waiverLines.clear();\n sinks.length = 0;\n },\n \"Program:exit\"() {\n sinks.sort(\n (left, right) => left.node.range[0] - right.node.range[0],\n );\n for (const { node, kind } of sinks) {\n const waived = consumeWaiver(node);\n if (kind === \"safe\" || (kind === \"unsafe\" && waived)) continue;\n context.report({\n node,\n messageId: messageIdFor(kind),\n });\n }\n sinks.length = 0;\n },\n Program(node) {\n const comments: unknown[] = Array.isArray(node.comments)\n ? node.comments\n : [];\n for (const comment of comments) {\n if (isWaiverComment(comment)) {\n waiverLines.add(comment.loc.end.line);\n }\n }\n },\n\n JSXAttribute(node) {\n const name = jsxAttributeName(node.name);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === name);\n if (prop === undefined) return;\n const value = unwrapExpression(node.value);\n checkReactHtmlProp(\n prop,\n value?.type === \"JSXExpressionContainer\"\n ? value.expression\n : value,\n );\n },\n\n JSXSpreadAttribute(node) {\n checkReactProps(node.argument);\n },\n\n // Payload objects remain checked when hoisted or spread into JSX.\n // Direct value props use the JSX and factory props contexts.\n Property(node) {\n const key = objectPropertyKey(node);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n if (prop?.kind !== \"payload\") return;\n const container = node.parent;\n if (isAstNode(container) && container.type === \"ObjectExpression\") {\n checkReactHtmlProp(prop, node.value);\n }\n },\n\n AssignmentExpression(node) {\n const target = unwrapExpression(node.left);\n if (target?.type !== \"MemberExpression\") {\n return;\n }\n const name = memberPropertyName(target);\n if (name !== null && HTML_PROPERTIES.has(name)) {\n reportIfUnsafe(node.right);\n }\n },\n\n CallExpression(node) {\n const call: unknown = node;\n const callee = isAstNode(call) ? invokedCallee(call) : null;\n const invoked = invokedArguments(node);\n const factory = staticCalleeName(callee);\n if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {\n checkReactProps(invoked.args.at(1));\n return;\n }\n if (callee?.type !== \"MemberExpression\") {\n return;\n }\n const method = memberPropertyName(callee);\n if (method === null) {\n return;\n }\n if (HTML_FIRST_ARGUMENT_METHODS.has(method)) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(0));\n return;\n }\n if (method === \"insertAdjacentHTML\") {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(1));\n return;\n }\n const attributeSink = HTML_ATTRIBUTE_SINKS.find(\n (entry) => entry.name === method,\n );\n if (attributeSink !== undefined) {\n if (\n attributeSink.namespaceIndex !== null &&\n isKnownNonEmptyNamespace(\n invoked.args.at(attributeSink.namespaceIndex),\n context.sourceCode.getScope(node),\n )\n ) {\n return;\n }\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n const attribute = staticStringValue(\n invoked.args.at(attributeSink.nameIndex),\n );\n if (attribute?.toLowerCase() === \"srcdoc\") {\n reportIfUnsafe(invoked.args.at(attributeSink.valueIndex));\n }\n return;\n }\n if (\n DOCUMENT_WRITE_METHODS.has(method) &&\n isDocumentSinkReceiver(\n callee.object,\n context.sourceCode.getScope(node),\n )\n ) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n observeSink(\n node,\n invoked.args.every((argument) =>\n isProvenSafeValue(argument, sanitizerPolicy),\n )\n ? \"safe\"\n : \"unsafe\",\n );\n return;\n }\n if (isIdentifier(callee.object, \"Object\") && method === \"assign\") {\n const domTarget = isKnownDomTarget({\n node: invoked.args.at(0),\n scope: context.sourceCode.getScope(node),\n });\n checkHtmlPropertySources(\n [\n ...invoked.args.slice(1),\n ...(invoked.status === \"opaque\" ? invoked.tail : []),\n ],\n domTarget,\n );\n if (invoked.status === \"opaque\" && domTarget) {\n reportIfUnsafe(invoked.spread);\n }\n }\n },\n };\n },\n },\n },\n});\n\nexport default plugin;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-unsafe-inner-html.mjs","names":["exhaustive"],"sources":["../src/no-unsafe-inner-html.ts"],"sourcesContent":["// Forbid injecting un-proven HTML into the DOM.\n//\n// Raw HTML may only reach the DOM from a value that is provably sanitized or\n// escaped. This rule covers common DOM and JSX sinks while allowing explicit\n// sanitizer calls and narrowly scoped provenance comments.\n//\n// Sinks, all sharing one allowlist:\n// • `__html` of a `dangerouslySetInnerHTML` object, whether the object is a\n// JSX attribute value or a property of any object literal (props passed\n// to `createElement` / `jsx()`, or spread into JSX).\n// • assignment to `innerHTML`, `outerHTML` or `srcdoc`, dot or static\n// bracket notation, and the same keys in an `Object.assign` source\n// (either branch of a conditional or logical source).\n// • the JSX `srcDoc` attribute.\n// • `insertAdjacentHTML`, `setHTMLUnsafe`, `createContextualFragment`,\n// `setAttribute(\"srcdoc\", …)` / `setAttributeNS(…, \"srcdoc\", …)`,\n// and `write` / `writeln` on a document.\n//\n// In the default `allowlist` policy, a value is allowed when it is:\n// • a static string Literal or TemplateLiteral whose interpolations are all\n// independently static, OR\n// • passed through the shared HTML sanitizer allowlist (`escapeHtml`,\n// `escapeXml`, `sanitizeHtml`, `sanitizeDom`, `DOMPurify.sanitize`, and\n// related established names), including `.value` / `.data` / `.html`\n// result accessors; OR\n// • annotated by a `// safe-html: <provenance>` comment with non-empty text\n// on the line directly above the sink. One comment covers one sink: a\n// second sink starting on the same line needs its own annotation.\n// The `provenance` policy omits the sanitizer-name allowlist and requires the\n// comment for dynamic sanitizer results as well.\n//\n// The sanitizer allowlist preserves the shared plugin contract. For dynamic\n// values outside that list, function names are not proof: a local identity\n// function can be named `sanitizeHtml`. Add explicit provenance at the sink\n// until the codebase has a branded SafeHtml boundary.\n\nimport type { Scope, Variable } from \"@oxlint/plugins\";\nimport { eslintCompatPlugin, type Plugin } from \"@oxlint/plugins\";\n\nimport type { AstNode, LiteralArgumentResolution } from \"./utils.ts\";\nimport {\n getPropertyName,\n invokedCallee,\n isAstNode,\n isIdentifier,\n isStringLiteral,\n memberPropertyName,\n resolveLiteralArraySpreads,\n staticStringValue,\n unwrapExpression,\n} from \"./utils.ts\";\n\ntype DomElementProducer = {\n name: string;\n kind: \"property\" | \"method\" | \"collection\" | \"collection-property\";\n receiver: \"document\" | \"element\" | \"either\";\n};\n\n// DOM element provenance and the generated equivalence corpus share this table.\nexport const DOM_ELEMENT_PRODUCERS = [\n { name: \"body\", kind: \"property\", receiver: \"document\" },\n { name: \"head\", kind: \"property\", receiver: \"document\" },\n { name: \"documentElement\", kind: \"property\", receiver: \"document\" },\n { name: \"activeElement\", kind: \"property\", receiver: \"document\" },\n { name: \"scrollingElement\", kind: \"property\", receiver: \"document\" },\n { name: \"fullscreenElement\", kind: \"property\", receiver: \"document\" },\n { name: \"createElement\", kind: \"method\", receiver: \"document\" },\n { name: \"createElementNS\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementById\", kind: \"method\", receiver: \"document\" },\n { name: \"querySelector\", kind: \"method\", receiver: \"either\" },\n { name: \"closest\", kind: \"method\", receiver: \"element\" },\n { name: \"elementFromPoint\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementsByClassName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagNameNS\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByName\", kind: \"collection\", receiver: \"document\" },\n { name: \"querySelectorAll\", kind: \"collection\", receiver: \"either\" },\n { name: \"children\", kind: \"collection-property\", receiver: \"element\" },\n { name: \"parentElement\", kind: \"property\", receiver: \"element\" },\n { name: \"firstElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"lastElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"nextElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"previousElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"offsetParent\", kind: \"property\", receiver: \"element\" },\n] as const;\n\n// The provenance text after the marker must be non-empty.\nconst WAIVER_RE = /^\\s*safe-html:\\s*\\S/u;\n\nexport const REACT_HTML_PROPS = [\n { name: \"dangerouslySetInnerHTML\", kind: \"payload\", htmlKey: \"__html\" },\n { name: \"srcDoc\", kind: \"value\" },\n { name: \"srcdoc\", kind: \"value\" },\n] as const;\ntype ReactHtmlProp = (typeof REACT_HTML_PROPS)[number];\nexport const REACT_ELEMENT_FACTORIES: ReadonlySet<string> = new Set([\n \"createElement\",\n \"React.createElement\",\n \"jsx\",\n \"jsxs\",\n \"jsxDEV\",\n]);\nconst HTML_PROPERTIES: ReadonlySet<string> = new Set([\n \"innerHTML\",\n \"outerHTML\",\n \"srcdoc\",\n]);\n// Methods whose first argument is parsed as HTML.\nconst HTML_FIRST_ARGUMENT_METHODS: ReadonlySet<string> = new Set([\n \"createContextualFragment\",\n \"setHTMLUnsafe\",\n]);\nexport const DOCUMENT_WRITE_METHODS: ReadonlySet<string> = new Set([\n \"write\",\n \"writeln\",\n]);\nexport const HTML_ATTRIBUTE_SINKS = [\n { name: \"setAttribute\", namespaceIndex: null, nameIndex: 0, valueIndex: 1 },\n { name: \"setAttributeNS\", namespaceIndex: 0, nameIndex: 1, valueIndex: 2 },\n] as const;\nconst DOCUMENT_MEMBERS: ReadonlySet<string> = new Set([\n \"contentDocument\",\n \"document\",\n \"ownerDocument\",\n]);\nconst FILE_SYSTEM_MODULES: ReadonlySet<string> = new Set([\n \"fs\",\n \"fs/promises\",\n \"node:fs\",\n \"node:fs/promises\",\n]);\nconst CHILD_PROCESS_MODULES: ReadonlySet<string> = new Set([\n \"child_process\",\n \"node:child_process\",\n]);\n// Calls that return a ChildProcess, whose stdin is a writable stream.\nconst CHILD_PROCESS_FACTORIES: ReadonlySet<string> = new Set([\n \"exec\",\n \"execFile\",\n \"fork\",\n \"spawn\",\n]);\nconst HTML_SANITIZER_LEAF_RE =\n /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;\nconst SANITIZER_RESULT_MEMBERS = new Set([\"value\", \"data\", \"html\"]);\nconst SANITIZER_POLICY = {\n allowlist: \"allowlist\",\n provenance: \"provenance\",\n} as const;\nconst SANITIZER_POLICY_VALUES = Object.values(SANITIZER_POLICY);\ntype SanitizerPolicy = (typeof SANITIZER_POLICY)[keyof typeof SANITIZER_POLICY];\n\nconst sanitizerPolicyFrom = (option: unknown): SanitizerPolicy => {\n if (option === undefined) {\n return SANITIZER_POLICY.allowlist;\n }\n if (typeof option !== \"object\" || option === null || Array.isArray(option)) {\n throw new Error(\"no-unsafe-inner-html options must be an object\");\n }\n const policy = Reflect.get(option, \"sanitizerPolicy\");\n switch (policy) {\n case undefined:\n case SANITIZER_POLICY.allowlist:\n return SANITIZER_POLICY.allowlist;\n case SANITIZER_POLICY.provenance:\n return SANITIZER_POLICY.provenance;\n default:\n throw new Error(`unknown sanitizer policy: ${String(policy)}`);\n }\n};\n\n// Use the same static member-name resolution for sinks and safe-value proofs.\nconst staticCalleeName = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (isIdentifier(expression)) return expression.name;\n if (expression?.type !== \"MemberExpression\") return null;\n const receiver = staticCalleeName(expression.object);\n const property = memberPropertyName(expression);\n return receiver === null || property === null\n ? null\n : `${receiver}.${property}`;\n};\n\nconst isSanitizerCall = (node: unknown): boolean => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return false;\n }\n const dotted = staticCalleeName(node.callee);\n if (dotted === null) {\n return false;\n }\n const parts = dotted.split(\".\");\n const leaf = parts.at(-1);\n if (leaf === undefined) {\n return false;\n }\n if (HTML_SANITIZER_LEAF_RE.test(leaf)) {\n return true;\n }\n return leaf === \"sanitize\" && /^dompurify$/iu.test(parts.at(-2) ?? \"\");\n};\n\nconst isSanitizerResult = (node: unknown): boolean => {\n const expression = unwrapExpression(node);\n if (expression?.type !== \"MemberExpression\") {\n return false;\n }\n const property = memberPropertyName(expression);\n return (\n property !== null &&\n SANITIZER_RESULT_MEMBERS.has(property) &&\n isSanitizerCall(expression.object)\n );\n};\n\ntype Located = { loc: { start: { line: number } } };\n\nconst hasLocation = (node: unknown): node is Located =>\n typeof node === \"object\" &&\n node !== null &&\n \"loc\" in node &&\n typeof node.loc === \"object\" &&\n node.loc !== null &&\n \"start\" in node.loc &&\n typeof node.loc.start === \"object\" &&\n node.loc.start !== null &&\n \"line\" in node.loc.start &&\n typeof node.loc.start.line === \"number\";\n\n// A node without location data never matches a waiver line.\nconst startLine = (node: unknown): number =>\n hasLocation(node) ? node.loc.start.line : Number.NaN;\n\ntype WaiverComment = {\n value: string;\n loc: { end: { line: number } };\n};\n\nconst isWaiverComment = (value: unknown): value is WaiverComment =>\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n typeof value.value === \"string\" &&\n WAIVER_RE.test(value.value) &&\n \"loc\" in value &&\n typeof value.loc === \"object\" &&\n value.loc !== null;\n\nconst jsxAttributeName = (node: unknown): string | null =>\n isAstNode(node) &&\n node.type === \"JSXIdentifier\" &&\n typeof node.name === \"string\"\n ? node.name\n : null;\n\n// A value is proven safe by its own static shape, independent of comments.\nconst isProvenSafeValue = (\n node: unknown,\n sanitizerPolicy: SanitizerPolicy,\n): boolean => {\n const expression = unwrapExpression(node);\n if (expression === null) {\n return false;\n }\n if (isStringLiteral(expression)) {\n return true;\n }\n if (\n sanitizerPolicy === SANITIZER_POLICY.allowlist &&\n (isSanitizerCall(expression) || isSanitizerResult(expression))\n ) {\n return true;\n }\n return (\n expression.type === \"TemplateLiteral\" &&\n Array.isArray(expression.expressions) &&\n expression.expressions.every((entry) =>\n isProvenSafeValue(entry, sanitizerPolicy),\n )\n );\n};\n\n// The static key of an object-literal Property: `a`, `\"a\"`, `[\"a\"]`.\nconst objectPropertyKey = (property: unknown): string | null => {\n if (!isAstNode(property)) {\n return null;\n }\n if (property.computed === true) {\n return staticStringValue(property.key);\n }\n return getPropertyName(property.key);\n};\n\nconst isOpaqueProperty = (property: AstNode): boolean =>\n property.type === \"SpreadElement\" ||\n (property.type === \"Property\" &&\n property.computed === true &&\n objectPropertyKey(property) === null);\n\nconst objectProperties = (node: unknown): AstNode[] => {\n const expression = unwrapExpression(node);\n if (\n expression?.type !== \"ObjectExpression\" ||\n !Array.isArray(expression.properties)\n ) {\n return [];\n }\n const properties: AstNode[] = [];\n for (const property of expression.properties.filter(isAstNode)) {\n if (\n property.type === \"SpreadElement\" &&\n unwrapExpression(property.argument)?.type === \"ObjectExpression\"\n ) {\n properties.push(...objectProperties(property.argument));\n continue;\n }\n properties.push(property);\n }\n return properties;\n};\n\ntype AppliedArguments = LiteralArgumentResolution;\n\nconst appliedArgumentsFor = (args: unknown[]): AppliedArguments => {\n const applied = unwrapExpression(args.at(1));\n if (\n applied === null ||\n (applied.type === \"Literal\" && applied.value === null) ||\n isIdentifier(applied, \"undefined\")\n ) {\n return { status: \"known\", args: [] };\n }\n if (applied.type !== \"ArrayExpression\" || !Array.isArray(applied.elements)) {\n return { status: \"opaque\", args: [], spread: args.at(1), tail: [] };\n }\n return resolveLiteralArraySpreads(applied.elements);\n};\n\n// The arguments the invoked function receives: `f.call(thisArg, ...args)`\n// shifts them by one; `f.apply(thisArg, [...])` forwards a literal array.\nconst invokedArguments = (call: unknown): AppliedArguments => {\n if (!isAstNode(call)) {\n return { status: \"known\", args: [] };\n }\n const rawArguments = Array.isArray(call.arguments) ? call.arguments : [];\n const resolved = resolveLiteralArraySpreads(rawArguments);\n const callee = unwrapExpression(call.callee);\n if (callee?.type !== \"MemberExpression\") return resolved;\n const method = memberPropertyName(callee);\n if (method === \"call\") {\n return { ...resolved, args: resolved.args.slice(1) };\n }\n if (method !== \"apply\") return resolved;\n if (resolved.status === \"opaque\") {\n if (resolved.args.length >= 2) return appliedArgumentsFor(resolved.args);\n return { ...resolved, args: [], tail: [] };\n }\n return appliedArgumentsFor(resolved.args);\n};\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"no-unsafe-inner-html\" },\n rules: {\n \"no-unsafe-inner-html\": {\n meta: {\n type: \"problem\",\n schema: [\n {\n type: \"object\",\n properties: {\n sanitizerPolicy: {\n type: \"string\",\n enum: SANITIZER_POLICY_VALUES,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n unsafeInnerHtml:\n \"Dynamic HTML injected into the DOM must be static, proven by \" +\n \"the configured sanitizer policy, or have an adjacent \" +\n \"`// safe-html: <provenance>` comment naming the trusted source. \" +\n \"Function names outside the allowlist are not proof because a \" +\n \"local identity function can shadow them.\",\n unsafeInnerHtmlSpread:\n \"Do not spread an object in dangerouslySetInnerHTML. Keep \" +\n \"`__html` inline so this rule can prove the HTML value is \" +\n \"sanitized or escaped.\",\n unsafeInnerHtmlComputedKey:\n \"Do not use an unresolved computed key in \" +\n \"dangerouslySetInnerHTML. Write an explicit `__html` property \" +\n \"so this rule can \" +\n \"prove the HTML value is sanitized or escaped.\",\n },\n },\n createOnce(context) {\n type BindingOptions = {\n node: unknown;\n scope: Scope;\n seen: Set<Variable>;\n };\n type BindingSource = {\n kind: \"expression\" | \"iteration\";\n node: AstNode | null;\n scope: Scope;\n };\n // All document, element and collection checks share one stable-binding path.\n const resolveStableBinding = ({\n node,\n scope,\n seen,\n }: BindingOptions): BindingSource | null => {\n let target = unwrapExpression(node);\n while (isIdentifier(target)) {\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(target.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length === 0) {\n return { kind: \"expression\", node: target, scope };\n }\n if (seen.has(variable)) return null;\n seen.add(variable);\n const definition = variable.defs.at(0);\n // A never-reassigned class or function declaration holds a\n // function, which is neither an element nor a document.\n if (\n variable.defs.length === 1 &&\n (definition?.type === \"ClassName\" ||\n definition?.type === \"FunctionName\") &&\n (definition.node.type === \"ClassDeclaration\" ||\n definition.node.type === \"FunctionDeclaration\") &&\n !variable.references.some((reference) => reference.isWrite())\n ) {\n return {\n kind: \"expression\",\n node: unwrapExpression(definition.node),\n scope,\n };\n }\n if (\n variable.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return null;\n }\n const declarator = definition.node;\n if (declarator.init !== null) {\n target = unwrapExpression(declarator.init);\n scope = context.sourceCode.getScope(declarator);\n continue;\n }\n const declaration = declarator.parent;\n if (declaration.type !== \"VariableDeclaration\") return null;\n const loop = declaration.parent;\n if (loop.type !== \"ForOfStatement\" || loop.left !== declaration) {\n return null;\n }\n return {\n kind: \"iteration\",\n node: unwrapExpression(loop.right),\n scope: context.sourceCode.getScope(loop.right),\n };\n }\n return { kind: \"expression\", node: target, scope };\n };\n const isKnownNonEmptyNamespace = (\n namespace: unknown,\n scope: Scope,\n ): boolean => {\n const source = resolveStableBinding({\n node: namespace,\n scope,\n seen: new Set<Variable>(),\n });\n if (source?.kind !== \"expression\") return false;\n const text = staticStringValue(source.node);\n // DOM normalizes the empty namespace to null. Unknowns stay checked.\n if (text !== null) return text !== \"\";\n if (source.node?.type !== \"Literal\") return false;\n const { value } = source.node;\n return (\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n );\n };\n\n const variableFor = (\n identifier: { name: string },\n initialScope: Scope,\n ): Variable | undefined => {\n let current: Scope | null = initialScope;\n while (current !== null) {\n const variable = current.set.get(identifier.name);\n if (variable !== undefined) return variable;\n current = current.upper;\n }\n return undefined;\n };\n const isKnownNonDocumentReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n const isUnshadowedGlobal = (\n identifier: unknown,\n name: string,\n ): boolean =>\n isIdentifier(identifier, name) &&\n variableFor(identifier, scope) === undefined;\n const isFileSystemImport = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n if (variable?.defs.length !== 1) return false;\n const definition = variable.defs.at(0);\n return (\n definition?.type === \"ImportBinding\" &&\n definition.parent?.type === \"ImportDeclaration\" &&\n FILE_SYSTEM_MODULES.has(definition.parent.source.value)\n );\n };\n // A const bound once to a child process factory call: a node\n // child_process import or the unshadowed Bun.spawn.\n const isChildProcess = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n const definition = variable?.defs.at(0);\n if (\n variable?.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\"\n )\n return false;\n const init = unwrapExpression(definition.node.init);\n if (init?.type !== \"CallExpression\") return false;\n const callee = unwrapExpression(init.callee);\n if (callee?.type === \"MemberExpression\")\n return (\n memberPropertyName(callee) === \"spawn\" &&\n isUnshadowedGlobal(unwrapExpression(callee.object), \"Bun\")\n );\n if (!isIdentifier(callee)) return false;\n const factory = variableFor(callee, scope);\n const binding = factory?.defs.at(0);\n return (\n factory?.defs.length === 1 &&\n binding?.type === \"ImportBinding\" &&\n binding.node.type === \"ImportSpecifier\" &&\n binding.node.imported.type === \"Identifier\" &&\n CHILD_PROCESS_FACTORIES.has(binding.node.imported.name) &&\n binding.parent?.type === \"ImportDeclaration\" &&\n CHILD_PROCESS_MODULES.has(binding.parent.source.value)\n );\n };\n const root = unwrapExpression(node);\n if (isUnshadowedGlobal(root, \"Bun\") || isFileSystemImport(root)) {\n return true;\n }\n if (root?.type !== \"MemberExpression\") return false;\n const stream = memberPropertyName(root);\n const owner = unwrapExpression(root.object);\n return (\n ((stream === \"stdout\" || stream === \"stderr\") &&\n isUnshadowedGlobal(owner, \"process\")) ||\n (stream === \"stdin\" && isChildProcess(owner))\n );\n };\n const isDocumentReceiver = (node: unknown, scope: Scope): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const source = resolveStableBinding({\n node,\n scope,\n seen: new Set<Variable>(),\n });\n if (source === null || source.kind !== \"expression\") return true;\n const receiver = source.node;\n if (isIdentifier(receiver, \"document\")) return true;\n if (receiver?.type !== \"MemberExpression\") return false;\n const name = memberPropertyName(receiver);\n return name !== null && DOCUMENT_MEMBERS.has(name);\n };\n\n const isDocumentSinkReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const receiver = unwrapExpression(node);\n if (!isIdentifier(receiver)) return true;\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(receiver.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length !== 1) return true;\n const definition = variable.defs.at(0);\n if (\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.init === null ||\n unwrapExpression(definition.node.init)?.type !==\n \"ObjectExpression\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\" ||\n variable.scope.variableScope !== scope.variableScope ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return true;\n }\n return false;\n };\n type DomTargetOptions = {\n node: unknown;\n scope: Scope;\n seen?: Set<Variable>;\n kind?: \"element\" | \"collection\";\n };\n const isKnownDomTarget = ({\n node,\n scope,\n seen = new Set<Variable>(),\n kind = \"element\",\n }: DomTargetOptions): boolean => {\n const source = resolveStableBinding({ node, scope, seen });\n // An unstable binding may have been reassigned to a DOM target. Fail\n // closed unless the resolver can prove a stable non-DOM value.\n if (source === null) return true;\n if (source.kind === \"iteration\") {\n return (\n kind === \"element\" &&\n isKnownDomTarget({\n node: source.node,\n scope: source.scope,\n seen,\n kind: \"collection\",\n })\n );\n }\n const target = source.node;\n scope = source.scope;\n const hasProducerReceiver = (\n receiver: unknown,\n producer: DomElementProducer,\n ): boolean => {\n if (\n producer.receiver !== \"element\" &&\n isDocumentReceiver(receiver, scope)\n ) {\n return true;\n }\n return (\n producer.receiver !== \"document\" &&\n isKnownDomTarget({ node: receiver, scope, seen })\n );\n };\n const isElementCollection = (collection: unknown): boolean =>\n isKnownDomTarget({\n node: collection,\n scope,\n seen,\n kind: \"collection\",\n });\n if (target?.type === \"MemberExpression\") {\n const property = memberPropertyName(target);\n const producer = (\n DOM_ELEMENT_PRODUCERS satisfies readonly DomElementProducer[]\n ).find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\"\n ? \"collection-property\"\n : \"property\") && entry.name === property,\n );\n if (producer !== undefined) {\n return hasProducerReceiver(target.object, producer);\n }\n const index = unwrapExpression(target.property);\n if (\n kind === \"element\" &&\n target.computed === true &&\n index?.type === \"Literal\" &&\n typeof index.value === \"number\" &&\n Number.isInteger(index.value) &&\n index.value >= 0\n ) {\n return isElementCollection(target.object);\n }\n }\n if (target?.type === \"CallExpression\") {\n const callee = invokedCallee(target);\n if (callee?.type !== \"MemberExpression\") return false;\n const method = memberPropertyName(callee);\n if (\n kind === \"element\" &&\n method === \"item\" &&\n isElementCollection(callee.object)\n ) {\n return true;\n }\n const producer = DOM_ELEMENT_PRODUCERS.find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\" ? \"collection\" : \"method\") &&\n entry.name === method,\n );\n return (\n producer !== undefined &&\n hasProducerReceiver(callee.object, producer)\n );\n }\n return kind === \"element\" && isDocumentReceiver(target, scope);\n };\n\n let sanitizerPolicy: SanitizerPolicy = SANITIZER_POLICY.allowlist;\n // Associate waivers after visiting the file, so source position decides\n // the first covered sink even when AST visitors inspect values out of order.\n const waiverLines = new Set<number>();\n type SinkObservation = {\n node: AstNode;\n kind:\n | \"safe\"\n | \"unsafe\"\n | \"opaque-payload-spread\"\n | \"opaque-payload-computed-key\";\n };\n const sinks: SinkObservation[] = [];\n const observeSink = (\n node: unknown,\n kind: SinkObservation[\"kind\"],\n ): void => {\n if (isAstNode(node)) sinks.push({ node, kind });\n };\n const messageIdFor = (kind: SinkObservation[\"kind\"]) => {\n if (kind === \"opaque-payload-spread\") {\n return \"unsafeInnerHtmlSpread\";\n }\n if (kind === \"opaque-payload-computed-key\") {\n return \"unsafeInnerHtmlComputedKey\";\n }\n return \"unsafeInnerHtml\";\n };\n\n const consumeWaiver = (node: unknown): boolean => {\n const line = startLine(node) - 1;\n if (!waiverLines.has(line)) {\n return false;\n }\n waiverLines.delete(line);\n return true;\n };\n\n const reportIfUnsafe = (node: unknown): void => {\n observeSink(\n node,\n isProvenSafeValue(node, sanitizerPolicy) ? \"safe\" : \"unsafe\",\n );\n };\n\n // The value of a `dangerouslySetInnerHTML` prop: an inline object has\n // its `__html` checked and its spreads rejected; anything else is a\n // hoisted payload whose HTML this rule cannot see.\n const checkDangerousPayload = (\n value: unknown,\n htmlKey: string,\n ): void => {\n const expression = unwrapExpression(value);\n if (expression?.type !== \"ObjectExpression\") {\n reportIfUnsafe(value);\n return;\n }\n for (const property of objectProperties(expression)) {\n if (isOpaqueProperty(property)) {\n observeSink(\n property,\n property.type === \"SpreadElement\"\n ? \"opaque-payload-spread\"\n : \"opaque-payload-computed-key\",\n );\n continue;\n }\n if (\n property.type === \"Property\" &&\n objectPropertyKey(property) === htmlKey\n ) {\n reportIfUnsafe(property.value);\n }\n }\n };\n\n const checkReactHtmlProp = (\n prop: ReactHtmlProp,\n value: unknown,\n ): void => {\n switch (prop.kind) {\n case \"payload\":\n checkDangerousPayload(value, prop.htmlKey);\n return;\n case \"value\":\n reportIfUnsafe(value);\n return;\n default: {\n const exhaustive: never = prop;\n throw new Error(`Unknown React HTML prop: ${String(exhaustive)}`);\n }\n }\n };\n\n const checkReactProps = (value: unknown): void => {\n for (const property of objectProperties(value)) {\n if (property.type !== \"Property\") continue;\n const key = objectPropertyKey(property);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n // Payload properties are checked once by the Property visitor.\n if (prop?.kind === \"value\")\n checkReactHtmlProp(prop, property.value);\n }\n };\n\n const checkHtmlPropertySources = (\n sources: unknown[],\n domTarget: boolean,\n ): void => {\n for (const source of sources) {\n const expression = unwrapExpression(source);\n // Each branch of a conditional or logical expression may be the\n // value Object.assign copies from; the condition itself is not,\n // and the left side of `&&` is copied only when falsy, so never\n // as an object.\n if (expression?.type === \"ConditionalExpression\") {\n checkHtmlPropertySources(\n [expression.consequent, expression.alternate],\n domTarget,\n );\n continue;\n }\n // Object.assign skips null and undefined sources.\n if (\n (expression?.type === \"Literal\" && expression.value === null) ||\n isIdentifier(expression, \"undefined\")\n ) {\n continue;\n }\n if (expression?.type === \"LogicalExpression\") {\n checkHtmlPropertySources(\n expression.operator === \"&&\"\n ? [expression.right]\n : [expression.left, expression.right],\n domTarget,\n );\n continue;\n }\n if (expression?.type !== \"ObjectExpression\") {\n if (domTarget) {\n reportIfUnsafe(source);\n }\n continue;\n }\n for (const property of objectProperties(source)) {\n if (isOpaqueProperty(property)) {\n if (domTarget) {\n reportIfUnsafe(property);\n }\n continue;\n }\n const key =\n property.type === \"Property\"\n ? objectPropertyKey(property)\n : null;\n if (key !== null && HTML_PROPERTIES.has(key)) {\n reportIfUnsafe(property.value);\n }\n }\n }\n };\n\n return {\n before() {\n sanitizerPolicy = sanitizerPolicyFrom(context.options.at(0));\n waiverLines.clear();\n sinks.length = 0;\n },\n \"Program:exit\"() {\n sinks.sort(\n (left, right) => left.node.range[0] - right.node.range[0],\n );\n for (const { node, kind } of sinks) {\n const waived = consumeWaiver(node);\n if (kind === \"safe\" || (kind === \"unsafe\" && waived)) continue;\n context.report({\n node,\n messageId: messageIdFor(kind),\n });\n }\n sinks.length = 0;\n },\n Program(node) {\n const comments: unknown[] = Array.isArray(node.comments)\n ? node.comments\n : [];\n for (const comment of comments) {\n if (isWaiverComment(comment)) {\n waiverLines.add(comment.loc.end.line);\n }\n }\n },\n\n JSXAttribute(node) {\n const name = jsxAttributeName(node.name);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === name);\n if (prop === undefined) return;\n const value = unwrapExpression(node.value);\n checkReactHtmlProp(\n prop,\n value?.type === \"JSXExpressionContainer\"\n ? value.expression\n : value,\n );\n },\n\n JSXSpreadAttribute(node) {\n checkReactProps(node.argument);\n },\n\n // Payload objects remain checked when hoisted or spread into JSX.\n // Direct value props use the JSX and factory props contexts.\n Property(node) {\n const key = objectPropertyKey(node);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n if (prop?.kind !== \"payload\") return;\n const container = node.parent;\n if (isAstNode(container) && container.type === \"ObjectExpression\") {\n checkReactHtmlProp(prop, node.value);\n }\n },\n\n AssignmentExpression(node) {\n const target = unwrapExpression(node.left);\n if (target?.type !== \"MemberExpression\") {\n return;\n }\n const name = memberPropertyName(target);\n if (name !== null && HTML_PROPERTIES.has(name)) {\n reportIfUnsafe(node.right);\n }\n },\n\n CallExpression(node) {\n const call: unknown = node;\n const callee = isAstNode(call) ? invokedCallee(call) : null;\n const invoked = invokedArguments(node);\n const factory = staticCalleeName(callee);\n if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {\n checkReactProps(invoked.args.at(1));\n return;\n }\n if (callee?.type !== \"MemberExpression\") {\n return;\n }\n const method = memberPropertyName(callee);\n if (method === null) {\n return;\n }\n if (HTML_FIRST_ARGUMENT_METHODS.has(method)) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(0));\n return;\n }\n if (method === \"insertAdjacentHTML\") {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(1));\n return;\n }\n const attributeSink = HTML_ATTRIBUTE_SINKS.find(\n (entry) => entry.name === method,\n );\n if (attributeSink !== undefined) {\n if (\n attributeSink.namespaceIndex !== null &&\n isKnownNonEmptyNamespace(\n invoked.args.at(attributeSink.namespaceIndex),\n context.sourceCode.getScope(node),\n )\n ) {\n return;\n }\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n const attribute = staticStringValue(\n invoked.args.at(attributeSink.nameIndex),\n );\n if (attribute?.toLowerCase() === \"srcdoc\") {\n reportIfUnsafe(invoked.args.at(attributeSink.valueIndex));\n }\n return;\n }\n if (\n DOCUMENT_WRITE_METHODS.has(method) &&\n isDocumentSinkReceiver(\n callee.object,\n context.sourceCode.getScope(node),\n )\n ) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n observeSink(\n node,\n invoked.args.every((argument) =>\n isProvenSafeValue(argument, sanitizerPolicy),\n )\n ? \"safe\"\n : \"unsafe\",\n );\n return;\n }\n if (isIdentifier(callee.object, \"Object\") && method === \"assign\") {\n const domTarget = isKnownDomTarget({\n node: invoked.args.at(0),\n scope: context.sourceCode.getScope(node),\n });\n checkHtmlPropertySources(\n [\n ...invoked.args.slice(1),\n ...(invoked.status === \"opaque\" ? invoked.tail : []),\n ],\n domTarget,\n );\n if (invoked.status === \"opaque\" && domTarget) {\n reportIfUnsafe(invoked.spread);\n }\n }\n },\n };\n },\n },\n },\n});\n\nexport default plugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"no-unsafe-inner-html.mjs","names":["exhaustive"],"sources":["../src/no-unsafe-inner-html.ts"],"sourcesContent":["// Forbid injecting un-proven HTML into the DOM.\n//\n// Raw HTML may only reach the DOM from a value that is provably sanitized or\n// escaped. This rule covers common DOM and JSX sinks while allowing explicit\n// sanitizer calls and narrowly scoped provenance comments.\n//\n// Sinks, all sharing one allowlist:\n// • `__html` of a `dangerouslySetInnerHTML` object, whether the object is a\n// JSX attribute value or a property of any object literal (props passed\n// to `createElement` / `jsx()`, or spread into JSX).\n// • assignment to `innerHTML`, `outerHTML` or `srcdoc`, dot or static\n// bracket notation, and the same keys in an `Object.assign` source\n// (either branch of a conditional or logical source).\n// • the JSX `srcDoc` attribute.\n// • `insertAdjacentHTML`, `setHTMLUnsafe`, `createContextualFragment`,\n// `setAttribute(\"srcdoc\", …)` / `setAttributeNS(…, \"srcdoc\", …)`,\n// and `write` / `writeln` on a document.\n//\n// In the default `allowlist` policy, a value is allowed when it is:\n// • a static string Literal or TemplateLiteral whose interpolations are all\n// independently static, OR\n// • passed through the shared HTML sanitizer allowlist (`escapeHtml`,\n// `escapeXml`, `sanitizeHtml`, `sanitizeDom`, `DOMPurify.sanitize`, and\n// related established names), including `.value` / `.data` / `.html`\n// result accessors; OR\n// • annotated by a `// safe-html: <provenance>` comment with non-empty text\n// on the line directly above the sink. One comment covers one sink: a\n// second sink starting on the same line needs its own annotation.\n// The `provenance` policy omits the sanitizer-name allowlist and requires the\n// comment for dynamic sanitizer results as well.\n//\n// The sanitizer allowlist preserves the shared plugin contract. For dynamic\n// values outside that list, function names are not proof: a local identity\n// function can be named `sanitizeHtml`. Add explicit provenance at the sink\n// until the codebase has a branded SafeHtml boundary.\n\nimport {\n eslintCompatPlugin,\n type Plugin,\n type Scope,\n type Variable,\n} from \"@oxlint/plugins\";\n\nimport type { AstNode, LiteralArgumentResolution } from \"./utils.ts\";\nimport {\n getPropertyName,\n invokedCallee,\n isAstNode,\n isIdentifier,\n isStringLiteral,\n memberPropertyName,\n resolveLiteralArraySpreads,\n staticStringValue,\n unwrapExpression,\n} from \"./utils.ts\";\n\ntype DomElementProducer = {\n name: string;\n kind: \"property\" | \"method\" | \"collection\" | \"collection-property\";\n receiver: \"document\" | \"element\" | \"either\";\n};\n\n// DOM element provenance and the generated equivalence corpus share this table.\nexport const DOM_ELEMENT_PRODUCERS = [\n { name: \"body\", kind: \"property\", receiver: \"document\" },\n { name: \"head\", kind: \"property\", receiver: \"document\" },\n { name: \"documentElement\", kind: \"property\", receiver: \"document\" },\n { name: \"activeElement\", kind: \"property\", receiver: \"document\" },\n { name: \"scrollingElement\", kind: \"property\", receiver: \"document\" },\n { name: \"fullscreenElement\", kind: \"property\", receiver: \"document\" },\n { name: \"createElement\", kind: \"method\", receiver: \"document\" },\n { name: \"createElementNS\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementById\", kind: \"method\", receiver: \"document\" },\n { name: \"querySelector\", kind: \"method\", receiver: \"either\" },\n { name: \"closest\", kind: \"method\", receiver: \"element\" },\n { name: \"elementFromPoint\", kind: \"method\", receiver: \"document\" },\n { name: \"getElementsByClassName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagName\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByTagNameNS\", kind: \"collection\", receiver: \"either\" },\n { name: \"getElementsByName\", kind: \"collection\", receiver: \"document\" },\n { name: \"querySelectorAll\", kind: \"collection\", receiver: \"either\" },\n { name: \"children\", kind: \"collection-property\", receiver: \"element\" },\n { name: \"parentElement\", kind: \"property\", receiver: \"element\" },\n { name: \"firstElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"lastElementChild\", kind: \"property\", receiver: \"element\" },\n { name: \"nextElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"previousElementSibling\", kind: \"property\", receiver: \"element\" },\n { name: \"offsetParent\", kind: \"property\", receiver: \"element\" },\n] as const;\n\n// The provenance text after the marker must be non-empty.\nconst WAIVER_RE = /^\\s*safe-html:\\s*\\S/u;\n\nexport const REACT_HTML_PROPS = [\n { name: \"dangerouslySetInnerHTML\", kind: \"payload\", htmlKey: \"__html\" },\n { name: \"srcDoc\", kind: \"value\" },\n { name: \"srcdoc\", kind: \"value\" },\n] as const;\ntype ReactHtmlProp = (typeof REACT_HTML_PROPS)[number];\nexport const REACT_ELEMENT_FACTORIES: ReadonlySet<string> = new Set([\n \"createElement\",\n \"React.createElement\",\n \"jsx\",\n \"jsxs\",\n \"jsxDEV\",\n]);\nconst HTML_PROPERTIES: ReadonlySet<string> = new Set([\n \"innerHTML\",\n \"outerHTML\",\n \"srcdoc\",\n]);\n// Methods whose first argument is parsed as HTML.\nconst HTML_FIRST_ARGUMENT_METHODS: ReadonlySet<string> = new Set([\n \"createContextualFragment\",\n \"setHTMLUnsafe\",\n]);\nexport const DOCUMENT_WRITE_METHODS: ReadonlySet<string> = new Set([\n \"write\",\n \"writeln\",\n]);\nexport const HTML_ATTRIBUTE_SINKS = [\n { name: \"setAttribute\", namespaceIndex: null, nameIndex: 0, valueIndex: 1 },\n { name: \"setAttributeNS\", namespaceIndex: 0, nameIndex: 1, valueIndex: 2 },\n] as const;\nconst DOCUMENT_MEMBERS: ReadonlySet<string> = new Set([\n \"contentDocument\",\n \"document\",\n \"ownerDocument\",\n]);\nconst FILE_SYSTEM_MODULES: ReadonlySet<string> = new Set([\n \"fs\",\n \"fs/promises\",\n \"node:fs\",\n \"node:fs/promises\",\n]);\nconst CHILD_PROCESS_MODULES: ReadonlySet<string> = new Set([\n \"child_process\",\n \"node:child_process\",\n]);\n// Calls that return a ChildProcess, whose stdin is a writable stream.\nconst CHILD_PROCESS_FACTORIES: ReadonlySet<string> = new Set([\n \"exec\",\n \"execFile\",\n \"fork\",\n \"spawn\",\n]);\nconst HTML_SANITIZER_LEAF_RE =\n /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;\nconst SANITIZER_RESULT_MEMBERS = new Set([\"value\", \"data\", \"html\"]);\nconst SANITIZER_POLICY = {\n allowlist: \"allowlist\",\n provenance: \"provenance\",\n} as const;\nconst SANITIZER_POLICY_VALUES = Object.values(SANITIZER_POLICY);\ntype SanitizerPolicy = (typeof SANITIZER_POLICY)[keyof typeof SANITIZER_POLICY];\n\nconst sanitizerPolicyFrom = (option: unknown): SanitizerPolicy => {\n if (option === undefined) {\n return SANITIZER_POLICY.allowlist;\n }\n if (typeof option !== \"object\" || option === null || Array.isArray(option)) {\n throw new Error(\"no-unsafe-inner-html options must be an object\");\n }\n const policy = Reflect.get(option, \"sanitizerPolicy\");\n switch (policy) {\n case undefined:\n case SANITIZER_POLICY.allowlist:\n return SANITIZER_POLICY.allowlist;\n case SANITIZER_POLICY.provenance:\n return SANITIZER_POLICY.provenance;\n default:\n throw new Error(`unknown sanitizer policy: ${String(policy)}`);\n }\n};\n\n// Use the same static member-name resolution for sinks and safe-value proofs.\nconst staticCalleeName = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (isIdentifier(expression)) return expression.name;\n if (expression?.type !== \"MemberExpression\") return null;\n const receiver = staticCalleeName(expression.object);\n const property = memberPropertyName(expression);\n return receiver === null || property === null\n ? null\n : `${receiver}.${property}`;\n};\n\nconst isSanitizerCall = (node: unknown): boolean => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return false;\n }\n const dotted = staticCalleeName(node.callee);\n if (dotted === null) {\n return false;\n }\n const parts = dotted.split(\".\");\n const leaf = parts.at(-1);\n if (leaf === undefined) {\n return false;\n }\n if (HTML_SANITIZER_LEAF_RE.test(leaf)) {\n return true;\n }\n return leaf === \"sanitize\" && /^dompurify$/iu.test(parts.at(-2) ?? \"\");\n};\n\nconst isSanitizerResult = (node: unknown): boolean => {\n const expression = unwrapExpression(node);\n if (expression?.type !== \"MemberExpression\") {\n return false;\n }\n const property = memberPropertyName(expression);\n return (\n property !== null &&\n SANITIZER_RESULT_MEMBERS.has(property) &&\n isSanitizerCall(expression.object)\n );\n};\n\ntype Located = { loc: { start: { line: number } } };\n\nconst hasLocation = (node: unknown): node is Located =>\n typeof node === \"object\" &&\n node !== null &&\n \"loc\" in node &&\n typeof node.loc === \"object\" &&\n node.loc !== null &&\n \"start\" in node.loc &&\n typeof node.loc.start === \"object\" &&\n node.loc.start !== null &&\n \"line\" in node.loc.start &&\n typeof node.loc.start.line === \"number\";\n\n// A node without location data never matches a waiver line.\nconst startLine = (node: unknown): number =>\n hasLocation(node) ? node.loc.start.line : Number.NaN;\n\ntype WaiverComment = {\n value: string;\n loc: { end: { line: number } };\n};\n\nconst isWaiverComment = (value: unknown): value is WaiverComment =>\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n typeof value.value === \"string\" &&\n WAIVER_RE.test(value.value) &&\n \"loc\" in value &&\n typeof value.loc === \"object\" &&\n value.loc !== null;\n\nconst jsxAttributeName = (node: unknown): string | null =>\n isAstNode(node) &&\n node.type === \"JSXIdentifier\" &&\n typeof node.name === \"string\"\n ? node.name\n : null;\n\n// A value is proven safe by its own static shape, independent of comments.\nconst isProvenSafeValue = (\n node: unknown,\n sanitizerPolicy: SanitizerPolicy,\n): boolean => {\n const expression = unwrapExpression(node);\n if (expression === null) {\n return false;\n }\n if (isStringLiteral(expression)) {\n return true;\n }\n if (\n sanitizerPolicy === SANITIZER_POLICY.allowlist &&\n (isSanitizerCall(expression) || isSanitizerResult(expression))\n ) {\n return true;\n }\n return (\n expression.type === \"TemplateLiteral\" &&\n Array.isArray(expression.expressions) &&\n expression.expressions.every((entry) =>\n isProvenSafeValue(entry, sanitizerPolicy),\n )\n );\n};\n\n// The static key of an object-literal Property: `a`, `\"a\"`, `[\"a\"]`.\nconst objectPropertyKey = (property: unknown): string | null => {\n if (!isAstNode(property)) {\n return null;\n }\n if (property.computed === true) {\n return staticStringValue(property.key);\n }\n return getPropertyName(property.key);\n};\n\nconst isOpaqueProperty = (property: AstNode): boolean =>\n property.type === \"SpreadElement\" ||\n (property.type === \"Property\" &&\n property.computed === true &&\n objectPropertyKey(property) === null);\n\nconst objectProperties = (node: unknown): AstNode[] => {\n const expression = unwrapExpression(node);\n if (\n expression?.type !== \"ObjectExpression\" ||\n !Array.isArray(expression.properties)\n ) {\n return [];\n }\n const properties: AstNode[] = [];\n for (const property of expression.properties.filter(isAstNode)) {\n if (\n property.type === \"SpreadElement\" &&\n unwrapExpression(property.argument)?.type === \"ObjectExpression\"\n ) {\n properties.push(...objectProperties(property.argument));\n continue;\n }\n properties.push(property);\n }\n return properties;\n};\n\ntype AppliedArguments = LiteralArgumentResolution;\n\nconst appliedArgumentsFor = (args: unknown[]): AppliedArguments => {\n const applied = unwrapExpression(args.at(1));\n if (\n applied === null ||\n (applied.type === \"Literal\" && applied.value === null) ||\n isIdentifier(applied, \"undefined\")\n ) {\n return { status: \"known\", args: [] };\n }\n if (applied.type !== \"ArrayExpression\" || !Array.isArray(applied.elements)) {\n return { status: \"opaque\", args: [], spread: args.at(1), tail: [] };\n }\n return resolveLiteralArraySpreads(applied.elements);\n};\n\n// The arguments the invoked function receives: `f.call(thisArg, ...args)`\n// shifts them by one; `f.apply(thisArg, [...])` forwards a literal array.\nconst invokedArguments = (call: unknown): AppliedArguments => {\n if (!isAstNode(call)) {\n return { status: \"known\", args: [] };\n }\n const rawArguments = Array.isArray(call.arguments) ? call.arguments : [];\n const resolved = resolveLiteralArraySpreads(rawArguments);\n const callee = unwrapExpression(call.callee);\n if (callee?.type !== \"MemberExpression\") return resolved;\n const method = memberPropertyName(callee);\n if (method === \"call\") {\n return { ...resolved, args: resolved.args.slice(1) };\n }\n if (method !== \"apply\") return resolved;\n if (resolved.status === \"opaque\") {\n if (resolved.args.length >= 2) return appliedArgumentsFor(resolved.args);\n return { ...resolved, args: [], tail: [] };\n }\n return appliedArgumentsFor(resolved.args);\n};\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"no-unsafe-inner-html\" },\n rules: {\n \"no-unsafe-inner-html\": {\n meta: {\n type: \"problem\",\n schema: [\n {\n type: \"object\",\n properties: {\n sanitizerPolicy: {\n type: \"string\",\n enum: SANITIZER_POLICY_VALUES,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n unsafeInnerHtml:\n \"Dynamic HTML injected into the DOM must be static, proven by \" +\n \"the configured sanitizer policy, or have an adjacent \" +\n \"`// safe-html: <provenance>` comment naming the trusted source. \" +\n \"Function names outside the allowlist are not proof because a \" +\n \"local identity function can shadow them.\",\n unsafeInnerHtmlSpread:\n \"Do not spread an object in dangerouslySetInnerHTML. Keep \" +\n \"`__html` inline so this rule can prove the HTML value is \" +\n \"sanitized or escaped.\",\n unsafeInnerHtmlComputedKey:\n \"Do not use an unresolved computed key in \" +\n \"dangerouslySetInnerHTML. Write an explicit `__html` property \" +\n \"so this rule can \" +\n \"prove the HTML value is sanitized or escaped.\",\n },\n },\n createOnce(context) {\n type BindingOptions = {\n node: unknown;\n scope: Scope;\n seen: Set<Variable>;\n };\n type BindingSource = {\n kind: \"expression\" | \"iteration\";\n node: AstNode | null;\n scope: Scope;\n };\n // All document, element and collection checks share one stable-binding path.\n const resolveStableBinding = ({\n node,\n scope,\n seen,\n }: BindingOptions): BindingSource | null => {\n let target = unwrapExpression(node);\n while (isIdentifier(target)) {\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(target.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length === 0) {\n return { kind: \"expression\", node: target, scope };\n }\n if (seen.has(variable)) return null;\n seen.add(variable);\n const definition = variable.defs.at(0);\n // A never-reassigned class or function declaration holds a\n // function, which is neither an element nor a document.\n if (\n variable.defs.length === 1 &&\n (definition?.type === \"ClassName\" ||\n definition?.type === \"FunctionName\") &&\n (definition.node.type === \"ClassDeclaration\" ||\n definition.node.type === \"FunctionDeclaration\") &&\n !variable.references.some((reference) => reference.isWrite())\n ) {\n return {\n kind: \"expression\",\n node: unwrapExpression(definition.node),\n scope,\n };\n }\n if (\n variable.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return null;\n }\n const declarator = definition.node;\n if (declarator.init !== null) {\n target = unwrapExpression(declarator.init);\n scope = context.sourceCode.getScope(declarator);\n continue;\n }\n const declaration = declarator.parent;\n if (declaration.type !== \"VariableDeclaration\") return null;\n const loop = declaration.parent;\n if (loop.type !== \"ForOfStatement\" || loop.left !== declaration) {\n return null;\n }\n return {\n kind: \"iteration\",\n node: unwrapExpression(loop.right),\n scope: context.sourceCode.getScope(loop.right),\n };\n }\n return { kind: \"expression\", node: target, scope };\n };\n const isKnownNonEmptyNamespace = (\n namespace: unknown,\n scope: Scope,\n ): boolean => {\n const source = resolveStableBinding({\n node: namespace,\n scope,\n seen: new Set<Variable>(),\n });\n if (source?.kind !== \"expression\") return false;\n const text = staticStringValue(source.node);\n // DOM normalizes the empty namespace to null. Unknowns stay checked.\n if (text !== null) return text !== \"\";\n if (source.node?.type !== \"Literal\") return false;\n const { value } = source.node;\n return (\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n );\n };\n\n const variableFor = (\n identifier: { name: string },\n initialScope: Scope,\n ): Variable | undefined => {\n let current: Scope | null = initialScope;\n while (current !== null) {\n const variable = current.set.get(identifier.name);\n if (variable !== undefined) return variable;\n current = current.upper;\n }\n return undefined;\n };\n const isKnownNonDocumentReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n const isUnshadowedGlobal = (\n identifier: unknown,\n name: string,\n ): boolean =>\n isIdentifier(identifier, name) &&\n variableFor(identifier, scope) === undefined;\n const isFileSystemImport = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n if (variable?.defs.length !== 1) return false;\n const definition = variable.defs.at(0);\n return (\n definition?.type === \"ImportBinding\" &&\n definition.parent?.type === \"ImportDeclaration\" &&\n FILE_SYSTEM_MODULES.has(definition.parent.source.value)\n );\n };\n // A const bound once to a child process factory call: a node\n // child_process import or the unshadowed Bun.spawn.\n const isChildProcess = (identifier: unknown): boolean => {\n if (!isIdentifier(identifier)) return false;\n const variable = variableFor(identifier, scope);\n const definition = variable?.defs.at(0);\n if (\n variable?.defs.length !== 1 ||\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\"\n )\n return false;\n const init = unwrapExpression(definition.node.init);\n if (init?.type !== \"CallExpression\") return false;\n const callee = unwrapExpression(init.callee);\n if (callee?.type === \"MemberExpression\")\n return (\n memberPropertyName(callee) === \"spawn\" &&\n isUnshadowedGlobal(unwrapExpression(callee.object), \"Bun\")\n );\n if (!isIdentifier(callee)) return false;\n const factory = variableFor(callee, scope);\n const binding = factory?.defs.at(0);\n return (\n factory?.defs.length === 1 &&\n binding?.type === \"ImportBinding\" &&\n binding.node.type === \"ImportSpecifier\" &&\n binding.node.imported.type === \"Identifier\" &&\n CHILD_PROCESS_FACTORIES.has(binding.node.imported.name) &&\n binding.parent?.type === \"ImportDeclaration\" &&\n CHILD_PROCESS_MODULES.has(binding.parent.source.value)\n );\n };\n const root = unwrapExpression(node);\n if (isUnshadowedGlobal(root, \"Bun\") || isFileSystemImport(root)) {\n return true;\n }\n if (root?.type !== \"MemberExpression\") return false;\n const stream = memberPropertyName(root);\n const owner = unwrapExpression(root.object);\n return (\n ((stream === \"stdout\" || stream === \"stderr\") &&\n isUnshadowedGlobal(owner, \"process\")) ||\n (stream === \"stdin\" && isChildProcess(owner))\n );\n };\n const isDocumentReceiver = (node: unknown, scope: Scope): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const source = resolveStableBinding({\n node,\n scope,\n seen: new Set<Variable>(),\n });\n if (source === null || source.kind !== \"expression\") return true;\n const receiver = source.node;\n if (isIdentifier(receiver, \"document\")) return true;\n if (receiver?.type !== \"MemberExpression\") return false;\n const name = memberPropertyName(receiver);\n return name !== null && DOCUMENT_MEMBERS.has(name);\n };\n\n const isDocumentSinkReceiver = (\n node: unknown,\n scope: Scope,\n ): boolean => {\n if (isKnownNonDocumentReceiver(node, scope)) return false;\n const receiver = unwrapExpression(node);\n if (!isIdentifier(receiver)) return true;\n let current: Scope | null = scope;\n let variable: Variable | undefined;\n while (current !== null) {\n variable = current.set.get(receiver.name);\n if (variable !== undefined) break;\n current = current.upper;\n }\n if (variable === undefined || variable.defs.length !== 1) return true;\n const definition = variable.defs.at(0);\n if (\n definition?.type !== \"Variable\" ||\n definition.node.type !== \"VariableDeclarator\" ||\n definition.node.id.type !== \"Identifier\" ||\n definition.node.init === null ||\n unwrapExpression(definition.node.init)?.type !==\n \"ObjectExpression\" ||\n definition.node.parent.type !== \"VariableDeclaration\" ||\n definition.node.parent.kind !== \"const\" ||\n variable.scope.variableScope !== scope.variableScope ||\n variable.references.some(\n (reference) => reference.isWrite() && !reference.init,\n )\n ) {\n return true;\n }\n return false;\n };\n type DomTargetOptions = {\n node: unknown;\n scope: Scope;\n seen?: Set<Variable>;\n kind?: \"element\" | \"collection\";\n };\n const isKnownDomTarget = ({\n node,\n scope,\n seen = new Set<Variable>(),\n kind = \"element\",\n }: DomTargetOptions): boolean => {\n const source = resolveStableBinding({ node, scope, seen });\n // An unstable binding may have been reassigned to a DOM target. Fail\n // closed unless the resolver can prove a stable non-DOM value.\n if (source === null) return true;\n if (source.kind === \"iteration\") {\n return (\n kind === \"element\" &&\n isKnownDomTarget({\n node: source.node,\n scope: source.scope,\n seen,\n kind: \"collection\",\n })\n );\n }\n const target = source.node;\n scope = source.scope;\n const hasProducerReceiver = (\n receiver: unknown,\n producer: DomElementProducer,\n ): boolean => {\n if (\n producer.receiver !== \"element\" &&\n isDocumentReceiver(receiver, scope)\n ) {\n return true;\n }\n return (\n producer.receiver !== \"document\" &&\n isKnownDomTarget({ node: receiver, scope, seen })\n );\n };\n const isElementCollection = (collection: unknown): boolean =>\n isKnownDomTarget({\n node: collection,\n scope,\n seen,\n kind: \"collection\",\n });\n if (target?.type === \"MemberExpression\") {\n const property = memberPropertyName(target);\n const producer = (\n DOM_ELEMENT_PRODUCERS satisfies readonly DomElementProducer[]\n ).find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\"\n ? \"collection-property\"\n : \"property\") && entry.name === property,\n );\n if (producer !== undefined) {\n return hasProducerReceiver(target.object, producer);\n }\n const index = unwrapExpression(target.property);\n if (\n kind === \"element\" &&\n target.computed === true &&\n index?.type === \"Literal\" &&\n typeof index.value === \"number\" &&\n Number.isInteger(index.value) &&\n index.value >= 0\n ) {\n return isElementCollection(target.object);\n }\n }\n if (target?.type === \"CallExpression\") {\n const callee = invokedCallee(target);\n if (callee?.type !== \"MemberExpression\") return false;\n const method = memberPropertyName(callee);\n if (\n kind === \"element\" &&\n method === \"item\" &&\n isElementCollection(callee.object)\n ) {\n return true;\n }\n const producer = DOM_ELEMENT_PRODUCERS.find(\n (entry) =>\n entry.kind ===\n (kind === \"collection\" ? \"collection\" : \"method\") &&\n entry.name === method,\n );\n return (\n producer !== undefined &&\n hasProducerReceiver(callee.object, producer)\n );\n }\n return kind === \"element\" && isDocumentReceiver(target, scope);\n };\n\n let sanitizerPolicy: SanitizerPolicy = SANITIZER_POLICY.allowlist;\n // Associate waivers after visiting the file, so source position decides\n // the first covered sink even when AST visitors inspect values out of order.\n const waiverLines = new Set<number>();\n type SinkObservation = {\n node: AstNode;\n kind:\n | \"safe\"\n | \"unsafe\"\n | \"opaque-payload-spread\"\n | \"opaque-payload-computed-key\";\n };\n const sinks: SinkObservation[] = [];\n const observeSink = (\n node: unknown,\n kind: SinkObservation[\"kind\"],\n ): void => {\n if (isAstNode(node)) sinks.push({ node, kind });\n };\n const messageIdFor = (kind: SinkObservation[\"kind\"]) => {\n if (kind === \"opaque-payload-spread\") {\n return \"unsafeInnerHtmlSpread\";\n }\n if (kind === \"opaque-payload-computed-key\") {\n return \"unsafeInnerHtmlComputedKey\";\n }\n return \"unsafeInnerHtml\";\n };\n\n const consumeWaiver = (node: unknown): boolean => {\n const line = startLine(node) - 1;\n if (!waiverLines.has(line)) {\n return false;\n }\n waiverLines.delete(line);\n return true;\n };\n\n const reportIfUnsafe = (node: unknown): void => {\n observeSink(\n node,\n isProvenSafeValue(node, sanitizerPolicy) ? \"safe\" : \"unsafe\",\n );\n };\n\n // The value of a `dangerouslySetInnerHTML` prop: an inline object has\n // its `__html` checked and its spreads rejected; anything else is a\n // hoisted payload whose HTML this rule cannot see.\n const checkDangerousPayload = (\n value: unknown,\n htmlKey: string,\n ): void => {\n const expression = unwrapExpression(value);\n if (expression?.type !== \"ObjectExpression\") {\n reportIfUnsafe(value);\n return;\n }\n for (const property of objectProperties(expression)) {\n if (isOpaqueProperty(property)) {\n observeSink(\n property,\n property.type === \"SpreadElement\"\n ? \"opaque-payload-spread\"\n : \"opaque-payload-computed-key\",\n );\n continue;\n }\n if (\n property.type === \"Property\" &&\n objectPropertyKey(property) === htmlKey\n ) {\n reportIfUnsafe(property.value);\n }\n }\n };\n\n const checkReactHtmlProp = (\n prop: ReactHtmlProp,\n value: unknown,\n ): void => {\n switch (prop.kind) {\n case \"payload\":\n checkDangerousPayload(value, prop.htmlKey);\n return;\n case \"value\":\n reportIfUnsafe(value);\n return;\n default: {\n const exhaustive: never = prop;\n throw new Error(`Unknown React HTML prop: ${String(exhaustive)}`);\n }\n }\n };\n\n const checkReactProps = (value: unknown): void => {\n for (const property of objectProperties(value)) {\n if (property.type !== \"Property\") continue;\n const key = objectPropertyKey(property);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n // Payload properties are checked once by the Property visitor.\n if (prop?.kind === \"value\")\n checkReactHtmlProp(prop, property.value);\n }\n };\n\n const checkHtmlPropertySources = (\n sources: unknown[],\n domTarget: boolean,\n ): void => {\n for (const source of sources) {\n const expression = unwrapExpression(source);\n // Each branch of a conditional or logical expression may be the\n // value Object.assign copies from; the condition itself is not,\n // and the left side of `&&` is copied only when falsy, so never\n // as an object.\n if (expression?.type === \"ConditionalExpression\") {\n checkHtmlPropertySources(\n [expression.consequent, expression.alternate],\n domTarget,\n );\n continue;\n }\n // Object.assign skips null and undefined sources.\n if (\n (expression?.type === \"Literal\" && expression.value === null) ||\n isIdentifier(expression, \"undefined\")\n ) {\n continue;\n }\n if (expression?.type === \"LogicalExpression\") {\n checkHtmlPropertySources(\n expression.operator === \"&&\"\n ? [expression.right]\n : [expression.left, expression.right],\n domTarget,\n );\n continue;\n }\n if (expression?.type !== \"ObjectExpression\") {\n if (domTarget) {\n reportIfUnsafe(source);\n }\n continue;\n }\n for (const property of objectProperties(source)) {\n if (isOpaqueProperty(property)) {\n if (domTarget) {\n reportIfUnsafe(property);\n }\n continue;\n }\n const key =\n property.type === \"Property\"\n ? objectPropertyKey(property)\n : null;\n if (key !== null && HTML_PROPERTIES.has(key)) {\n reportIfUnsafe(property.value);\n }\n }\n }\n };\n\n return {\n before() {\n sanitizerPolicy = sanitizerPolicyFrom(context.options.at(0));\n waiverLines.clear();\n sinks.length = 0;\n },\n \"Program:exit\"() {\n sinks.sort(\n (left, right) => left.node.range[0] - right.node.range[0],\n );\n for (const { node, kind } of sinks) {\n const waived = consumeWaiver(node);\n if (kind === \"safe\" || (kind === \"unsafe\" && waived)) continue;\n context.report({\n node,\n messageId: messageIdFor(kind),\n });\n }\n sinks.length = 0;\n },\n Program(node) {\n const comments: unknown[] = Array.isArray(node.comments)\n ? node.comments\n : [];\n for (const comment of comments) {\n if (isWaiverComment(comment)) {\n waiverLines.add(comment.loc.end.line);\n }\n }\n },\n\n JSXAttribute(node) {\n const name = jsxAttributeName(node.name);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === name);\n if (prop === undefined) return;\n const value = unwrapExpression(node.value);\n checkReactHtmlProp(\n prop,\n value?.type === \"JSXExpressionContainer\"\n ? value.expression\n : value,\n );\n },\n\n JSXSpreadAttribute(node) {\n checkReactProps(node.argument);\n },\n\n // Payload objects remain checked when hoisted or spread into JSX.\n // Direct value props use the JSX and factory props contexts.\n Property(node) {\n const key = objectPropertyKey(node);\n const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);\n if (prop?.kind !== \"payload\") return;\n const container = node.parent;\n if (isAstNode(container) && container.type === \"ObjectExpression\") {\n checkReactHtmlProp(prop, node.value);\n }\n },\n\n AssignmentExpression(node) {\n const target = unwrapExpression(node.left);\n if (target?.type !== \"MemberExpression\") {\n return;\n }\n const name = memberPropertyName(target);\n if (name !== null && HTML_PROPERTIES.has(name)) {\n reportIfUnsafe(node.right);\n }\n },\n\n CallExpression(node) {\n const call: unknown = node;\n const callee = isAstNode(call) ? invokedCallee(call) : null;\n const invoked = invokedArguments(node);\n const factory = staticCalleeName(callee);\n if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {\n checkReactProps(invoked.args.at(1));\n return;\n }\n if (callee?.type !== \"MemberExpression\") {\n return;\n }\n const method = memberPropertyName(callee);\n if (method === null) {\n return;\n }\n if (HTML_FIRST_ARGUMENT_METHODS.has(method)) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(0));\n return;\n }\n if (method === \"insertAdjacentHTML\") {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n reportIfUnsafe(invoked.args.at(1));\n return;\n }\n const attributeSink = HTML_ATTRIBUTE_SINKS.find(\n (entry) => entry.name === method,\n );\n if (attributeSink !== undefined) {\n if (\n attributeSink.namespaceIndex !== null &&\n isKnownNonEmptyNamespace(\n invoked.args.at(attributeSink.namespaceIndex),\n context.sourceCode.getScope(node),\n )\n ) {\n return;\n }\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n const attribute = staticStringValue(\n invoked.args.at(attributeSink.nameIndex),\n );\n if (attribute?.toLowerCase() === \"srcdoc\") {\n reportIfUnsafe(invoked.args.at(attributeSink.valueIndex));\n }\n return;\n }\n if (\n DOCUMENT_WRITE_METHODS.has(method) &&\n isDocumentSinkReceiver(\n callee.object,\n context.sourceCode.getScope(node),\n )\n ) {\n if (invoked.status === \"opaque\") {\n reportIfUnsafe(node);\n return;\n }\n observeSink(\n node,\n invoked.args.every((argument) =>\n isProvenSafeValue(argument, sanitizerPolicy),\n )\n ? \"safe\"\n : \"unsafe\",\n );\n return;\n }\n if (isIdentifier(callee.object, \"Object\") && method === \"assign\") {\n const domTarget = isKnownDomTarget({\n node: invoked.args.at(0),\n scope: context.sourceCode.getScope(node),\n });\n checkHtmlPropertySources(\n [\n ...invoked.args.slice(1),\n ...(invoked.status === \"opaque\" ? invoked.tail : []),\n ],\n domTarget,\n );\n if (invoked.status === \"opaque\" && domTarget) {\n reportIfUnsafe(invoked.spread);\n }\n }\n },\n };\n },\n },\n },\n});\n\nexport default plugin;\n"],"mappings":"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"}
|
package/dist/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["// Shared AST helpers for the rules in this folder.\n//\n// Oxlint plugin AST nodes are passed in untyped. Each helper narrows from\n// `unknown` so rule files can call them without per-call type ceremony or\n// shared type-import boilerplate.\n\nimport type { ESTree, Ranged, Scope, Variable } from \"@oxlint/plugins\";\n\nexport type AstNode = Ranged & { type: string } & Record<string, unknown>;\n\ntype FilenameContext = {\n filename?: string;\n getFilename?: () => string;\n};\n\nexport const filenameForContext = (context: FilenameContext): string =>\n (context.filename ?? context.getFilename?.() ?? \"\").replaceAll(\"\\\\\", \"/\");\n\nexport const isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof (node as { type: unknown }).type === \"string\" &&\n \"range\" in node;\n\nexport const isIdentifier = (\n node: unknown,\n name?: string,\n): node is AstNode & { type: \"Identifier\"; name: string } => {\n if (!isAstNode(node) || node.type !== \"Identifier\") {\n return false;\n }\n if (typeof node.name !== \"string\") {\n return false;\n }\n return name === undefined || node.name === name;\n};\n\nexport const isStringLiteral = (\n node: unknown,\n): node is AstNode & { type: \"Literal\"; value: string } =>\n isAstNode(node) && node.type === \"Literal\" && typeof node.value === \"string\";\n\n// Resolve the static name of a Property or MemberExpression key:\n// Identifier.name or string-Literal.value. Returns null for computed keys\n// driven by a non-literal expression.\nexport const getPropertyName = (node: unknown): string | null => {\n if (isIdentifier(node)) {\n return node.name;\n }\n if (isStringLiteral(node)) {\n return node.value;\n }\n return null;\n};\n\nexport const staticStringValue = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (expression === null) return null;\n node = expression;\n if (isStringLiteral(node)) return node.value;\n if (\n !isAstNode(node) ||\n node.type !== \"TemplateLiteral\" ||\n !Array.isArray(node.expressions) ||\n node.expressions.length !== 0 ||\n !Array.isArray(node.quasis)\n )\n return null;\n const quasi = node.quasis.at(0);\n if (!isAstNode(quasi)) return null;\n const value = quasi.value;\n const cooked =\n typeof value === \"object\" && value !== null\n ? Reflect.get(value, \"cooked\")\n : undefined;\n return typeof cooked === \"string\" ? cooked : null;\n};\n\nexport const memberPropertyName = (node: unknown): string | null => {\n if (!isAstNode(node) || node.type !== \"MemberExpression\") {\n return null;\n }\n if (node.computed !== true) {\n return getPropertyName(node.property);\n }\n return staticStringValue(node.property);\n};\n\nexport const invokedCallee = (node: unknown): AstNode | null => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return null;\n }\n const callee = unwrapExpression(node.callee);\n if (!isAstNode(callee)) {\n return null;\n }\n if (\n callee.type === \"SequenceExpression\" &&\n Array.isArray(callee.expressions)\n ) {\n return unwrapExpression(callee.expressions.at(-1));\n }\n if (\n callee.type === \"MemberExpression\" &&\n (memberPropertyName(callee) === \"call\" ||\n memberPropertyName(callee) === \"apply\")\n ) {\n return unwrapExpression(callee.object);\n }\n return callee;\n};\n\n// Match `<object>.<property>` member access where both halves are\n// Identifiers and the access is not computed.\nexport const isMemberAccess = (\n node: unknown,\n object: string,\n property: string,\n): boolean =>\n isAstNode(node) &&\n node.type === \"MemberExpression\" &&\n node.computed === false &&\n isIdentifier(node.object, object) &&\n isIdentifier(node.property, property);\n\n// Match `CallExpression` whose callee is an Identifier with the given name.\nexport const isCallTo = (node: unknown, name: string): boolean =>\n isAstNode(node) &&\n node.type === \"CallExpression\" &&\n isIdentifier(node.callee, name);\n\n// Resolve the dot-notation name of a callee: an Identifier, or a\n// non-computed MemberExpression chain (e.g. `t.String`, `Schema.is`,\n// `process.stderr.write`). Returns null when the chain is computed\n// or the property name itself can't be resolved.\n//\n// If the chain is rooted at a non-Identifier expression (e.g. `foo().bar`),\n// returns the bare property name (\"bar\") rather than null. Callers that\n// match the result against a fixed allowlist must consider whether they\n// need to distinguish `foo().createSafeHandler` from `createSafeHandler`.\nexport const getCalleeName = (callee: unknown): string | null => {\n if (isIdentifier(callee)) {\n return callee.name;\n }\n if (!isAstNode(callee) || callee.type !== \"MemberExpression\") {\n return null;\n }\n if (callee.computed !== false) {\n return null;\n }\n const objectName = getCalleeName(callee.object);\n const propertyName = getPropertyName(callee.property);\n if (propertyName === null) {\n return null;\n }\n return objectName === null ? propertyName : `${objectName}.${propertyName}`;\n};\n\n// Parser wrappers with no value transformation of their own.\nexport const EXPRESSION_WRAPPER_TYPES = [\n \"ParenthesizedExpression\",\n \"ChainExpression\",\n \"TSNonNullExpression\",\n \"TSAsExpression\",\n \"TSSatisfiesExpression\",\n \"TSTypeAssertion\",\n \"TSInstantiationExpression\",\n] as const;\n\n// Keep the parser's other expression-bearing contexts intact.\nexport const unwrapExpression = (node: unknown): AstNode | null => {\n while (isAstNode(node)) {\n const { type } = node;\n if (\n !(EXPRESSION_WRAPPER_TYPES satisfies readonly ESTree.Node[\"type\"][]).some(\n (wrapper) => wrapper === type,\n )\n )\n return node;\n node = node.expression;\n }\n return null;\n};\n\nexport type LiteralArgumentResolution =\n | { status: \"known\"; args: unknown[] }\n | { status: \"opaque\"; args: unknown[]; spread: unknown; tail: unknown[] };\n\n// Expand literal array spreads and retain unresolved spread nodes in sequence.\n// Positional readers use only `args`, the prefix before the first barrier;\n// source inspectors can still inspect later entries in `tail`.\nexport const resolveLiteralArraySpreads = (\n args: unknown[],\n): LiteralArgumentResolution => {\n const expanded: unknown[] = [];\n const append = (argument: unknown): void => {\n if (!isAstNode(argument) || argument.type !== \"SpreadElement\") {\n expanded.push(argument);\n return;\n }\n const expression = unwrapExpression(argument.argument);\n if (\n expression?.type !== \"ArrayExpression\" ||\n !Array.isArray(expression.elements)\n ) {\n expanded.push(argument);\n return;\n }\n for (const element of expression.elements) append(element);\n };\n for (const argument of args) append(argument);\n const barrierIndex = expanded.findIndex(\n (argument) => isAstNode(argument) && argument.type === \"SpreadElement\",\n );\n if (barrierIndex === -1) return { status: \"known\", args: expanded };\n const spread = expanded.at(barrierIndex);\n return {\n status: \"opaque\",\n args: expanded.slice(0, barrierIndex),\n spread,\n tail: expanded.slice(barrierIndex + 1),\n };\n};\n\n// Resolve an ImportSpecifier's imported binding name (Identifier.name or\n// string-Literal.value). Returns null when the specifier shape is unexpected.\nexport const getImportedName = (specifier: unknown): string | null => {\n if (!isAstNode(specifier) || specifier.type !== \"ImportSpecifier\") {\n return null;\n }\n const imported = specifier.imported;\n if (isIdentifier(imported)) {\n return imported.name;\n }\n if (isStringLiteral(imported)) {\n return imported.value;\n }\n return null;\n};\n\n// Resolve an ImportSpecifier's local binding name. This differs from\n// getImportedName for aliased imports such as `import { source as local }`.\nexport const getImportLocalName = (specifier: unknown): string | null => {\n if (!isAstNode(specifier) || specifier.type !== \"ImportSpecifier\") {\n return null;\n }\n return isIdentifier(specifier.local) ? specifier.local.name : null;\n};\n\n// An Identifier the parser emitted as a value reference (it carries a range,\n// which synthetic name nodes do not).\nexport const isIdentifierReference = (\n node: unknown,\n): node is ESTree.IdentifierReference =>\n isIdentifier(node) && Array.isArray(node.range);\n\nexport type ScopeContext = {\n sourceCode: { getScope: (node: ESTree.Node) => Scope };\n};\n\nconst resolveVariable = (\n context: ScopeContext,\n identifier: ESTree.IdentifierReference,\n): Variable | null => {\n let scope: Scope | null = context.sourceCode.getScope(identifier);\n while (scope !== null) {\n const variable = scope.set.get(identifier.name);\n if (variable !== undefined) {\n return variable;\n }\n scope = scope.upper;\n }\n return null;\n};\n\nconst isSingleAssignment = (variable: Variable): boolean =>\n variable.references.every(\n (reference) => !reference.isWrite() || reference.init,\n );\n\ntype ImportedBinding = { source: string; imported: string };\nconst NAMESPACE_IMPORT = \"*\";\n\nconst memberOfNamespace = (\n base: ImportedBinding | null,\n property: string | null,\n): ImportedBinding | null =>\n base?.imported === NAMESPACE_IMPORT && property !== null\n ? { source: base.source, imported: property }\n : null;\n\nconst bindingFromVariable = (\n context: ScopeContext,\n variable: Variable,\n seen: Set<unknown>,\n): ImportedBinding | null => {\n const definition = variable.defs.at(0);\n if (variable.defs.length !== 1 || definition === undefined) {\n return null;\n }\n const node: unknown = definition.node;\n if (definition.type === \"ImportBinding\") {\n const declaration: unknown = definition.parent;\n if (\n !isAstNode(node) ||\n node.importKind === \"type\" ||\n !isAstNode(declaration) ||\n declaration.type !== \"ImportDeclaration\" ||\n declaration.importKind === \"type\" ||\n !isStringLiteral(declaration.source)\n ) {\n return null;\n }\n const source = declaration.source.value;\n if (node.type === \"ImportNamespaceSpecifier\") {\n return { source, imported: NAMESPACE_IMPORT };\n }\n if (node.type === \"ImportDefaultSpecifier\") {\n return { source, imported: \"default\" };\n }\n const imported = getImportedName(node);\n return imported === null ? null : { source, imported };\n }\n if (\n definition.type !== \"Variable\" ||\n !isAstNode(node) ||\n node.type !== \"VariableDeclarator\" ||\n !isSingleAssignment(variable) ||\n !isIdentifier(node.id)\n ) {\n return null;\n }\n return resolveImportedExpressionForEnforcement(context, node.init, seen);\n};\n\n// The module export an expression evaluates to, or null for a local,\n// parameter, call result, or unresolved global.\nexport const resolveImportedExpressionForEnforcement = (\n context: ScopeContext,\n node: unknown,\n seen: Set<unknown> = new Set(),\n): ImportedBinding | null => {\n const expression = unwrapExpression(node);\n if (!isAstNode(expression) || seen.has(expression)) {\n return null;\n }\n seen.add(expression);\n if (isIdentifierReference(expression)) {\n const variable = resolveVariable(context, expression);\n return variable === null\n ? null\n : bindingFromVariable(context, variable, seen);\n }\n if (expression.type === \"MemberExpression\") {\n const base = resolveImportedExpressionForEnforcement(\n context,\n expression.object,\n seen,\n );\n return memberOfNamespace(base, memberPropertyName(expression));\n }\n return null;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.cjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["// Shared AST helpers for the rules in this folder.\n//\n// Oxlint plugin AST nodes are passed in untyped. Each helper narrows from\n// `unknown` so rule files can call them without per-call type ceremony or\n// shared type-import boilerplate.\n\nimport type { ESTree, Ranged, Scope, Variable } from \"@oxlint/plugins\";\n\nexport type AstNode = Ranged & { type: string } & Record<string, unknown>;\n\ntype FilenameContext = {\n filename?: string;\n getFilename?: () => string;\n};\n\nexport const filenameForContext = (context: FilenameContext): string =>\n (context.filename ?? context.getFilename?.() ?? \"\").replaceAll(\"\\\\\", \"/\");\n\nexport const isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof node.type === \"string\" &&\n \"range\" in node;\n\nexport const isIdentifier = (\n node: unknown,\n name?: string,\n): node is AstNode & { type: \"Identifier\"; name: string } => {\n if (!isAstNode(node) || node.type !== \"Identifier\") {\n return false;\n }\n if (typeof node.name !== \"string\") {\n return false;\n }\n return name === undefined || node.name === name;\n};\n\nexport const isStringLiteral = (\n node: unknown,\n): node is AstNode & { type: \"Literal\"; value: string } =>\n isAstNode(node) && node.type === \"Literal\" && typeof node.value === \"string\";\n\n// Resolve the static name of a Property or MemberExpression key:\n// Identifier.name or string-Literal.value. Returns null for computed keys\n// driven by a non-literal expression.\nexport const getPropertyName = (node: unknown): string | null => {\n if (isIdentifier(node)) {\n return node.name;\n }\n if (isStringLiteral(node)) {\n return node.value;\n }\n return null;\n};\n\nexport const staticStringValue = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (expression === null) return null;\n node = expression;\n if (isStringLiteral(node)) return node.value;\n if (\n !isAstNode(node) ||\n node.type !== \"TemplateLiteral\" ||\n !Array.isArray(node.expressions) ||\n node.expressions.length !== 0 ||\n !Array.isArray(node.quasis)\n )\n return null;\n const quasi = node.quasis.at(0);\n if (!isAstNode(quasi)) return null;\n const value = quasi.value;\n const cooked =\n typeof value === \"object\" && value !== null\n ? Reflect.get(value, \"cooked\")\n : undefined;\n return typeof cooked === \"string\" ? cooked : null;\n};\n\nexport const memberPropertyName = (node: unknown): string | null => {\n if (!isAstNode(node) || node.type !== \"MemberExpression\") {\n return null;\n }\n if (node.computed !== true) {\n return getPropertyName(node.property);\n }\n return staticStringValue(node.property);\n};\n\nexport const invokedCallee = (node: unknown): AstNode | null => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return null;\n }\n const callee = unwrapExpression(node.callee);\n if (!isAstNode(callee)) {\n return null;\n }\n if (\n callee.type === \"SequenceExpression\" &&\n Array.isArray(callee.expressions)\n ) {\n return unwrapExpression(callee.expressions.at(-1));\n }\n if (\n callee.type === \"MemberExpression\" &&\n (memberPropertyName(callee) === \"call\" ||\n memberPropertyName(callee) === \"apply\")\n ) {\n return unwrapExpression(callee.object);\n }\n return callee;\n};\n\n// Match `<object>.<property>` member access where both halves are\n// Identifiers and the access is not computed.\nexport const isMemberAccess = (\n node: unknown,\n object: string,\n property: string,\n): boolean =>\n isAstNode(node) &&\n node.type === \"MemberExpression\" &&\n node.computed === false &&\n isIdentifier(node.object, object) &&\n isIdentifier(node.property, property);\n\n// Match `CallExpression` whose callee is an Identifier with the given name.\nexport const isCallTo = (node: unknown, name: string): boolean =>\n isAstNode(node) &&\n node.type === \"CallExpression\" &&\n isIdentifier(node.callee, name);\n\n// Resolve the dot-notation name of a callee: an Identifier, or a\n// non-computed MemberExpression chain (e.g. `t.String`, `Schema.is`,\n// `process.stderr.write`). Returns null when the chain is computed\n// or the property name itself can't be resolved.\n//\n// If the chain is rooted at a non-Identifier expression (e.g. `foo().bar`),\n// returns the bare property name (\"bar\") rather than null. Callers that\n// match the result against a fixed allowlist must consider whether they\n// need to distinguish `foo().createSafeHandler` from `createSafeHandler`.\nexport const getCalleeName = (callee: unknown): string | null => {\n if (isIdentifier(callee)) {\n return callee.name;\n }\n if (!isAstNode(callee) || callee.type !== \"MemberExpression\") {\n return null;\n }\n if (callee.computed !== false) {\n return null;\n }\n const objectName = getCalleeName(callee.object);\n const propertyName = getPropertyName(callee.property);\n if (propertyName === null) {\n return null;\n }\n return objectName === null ? propertyName : `${objectName}.${propertyName}`;\n};\n\n// Parser wrappers with no value transformation of their own.\nexport const EXPRESSION_WRAPPER_TYPES = [\n \"ParenthesizedExpression\",\n \"ChainExpression\",\n \"TSNonNullExpression\",\n \"TSAsExpression\",\n \"TSSatisfiesExpression\",\n \"TSTypeAssertion\",\n \"TSInstantiationExpression\",\n] as const;\n\n// Keep the parser's other expression-bearing contexts intact.\nexport const unwrapExpression = (node: unknown): AstNode | null => {\n while (isAstNode(node)) {\n const { type } = node;\n if (\n !(EXPRESSION_WRAPPER_TYPES satisfies readonly ESTree.Node[\"type\"][]).some(\n (wrapper) => wrapper === type,\n )\n )\n return node;\n node = node.expression;\n }\n return null;\n};\n\nexport type LiteralArgumentResolution =\n | { status: \"known\"; args: unknown[] }\n | { status: \"opaque\"; args: unknown[]; spread: unknown; tail: unknown[] };\n\n// Expand literal array spreads and retain unresolved spread nodes in sequence.\n// Positional readers use only `args`, the prefix before the first barrier;\n// source inspectors can still inspect later entries in `tail`.\nexport const resolveLiteralArraySpreads = (\n args: unknown[],\n): LiteralArgumentResolution => {\n const expanded: unknown[] = [];\n const append = (argument: unknown): void => {\n if (!isAstNode(argument) || argument.type !== \"SpreadElement\") {\n expanded.push(argument);\n return;\n }\n const expression = unwrapExpression(argument.argument);\n if (\n expression?.type !== \"ArrayExpression\" ||\n !Array.isArray(expression.elements)\n ) {\n expanded.push(argument);\n return;\n }\n for (const element of expression.elements) append(element);\n };\n for (const argument of args) append(argument);\n const barrierIndex = expanded.findIndex(\n (argument) => isAstNode(argument) && argument.type === \"SpreadElement\",\n );\n if (barrierIndex === -1) return { status: \"known\", args: expanded };\n const spread = expanded.at(barrierIndex);\n return {\n status: \"opaque\",\n args: expanded.slice(0, barrierIndex),\n spread,\n tail: expanded.slice(barrierIndex + 1),\n };\n};\n\n// Resolve an ImportSpecifier's imported binding name (Identifier.name or\n// string-Literal.value). Returns null when the specifier shape is unexpected.\nexport const getImportedName = (specifier: unknown): string | null => {\n if (!isAstNode(specifier) || specifier.type !== \"ImportSpecifier\") {\n return null;\n }\n const imported = specifier.imported;\n if (isIdentifier(imported)) {\n return imported.name;\n }\n if (isStringLiteral(imported)) {\n return imported.value;\n }\n return null;\n};\n\n// Resolve an ImportSpecifier's local binding name. This differs from\n// getImportedName for aliased imports such as `import { source as local }`.\nexport const getImportLocalName = (specifier: unknown): string | null => {\n if (!isAstNode(specifier) || specifier.type !== \"ImportSpecifier\") {\n return null;\n }\n return isIdentifier(specifier.local) ? specifier.local.name : null;\n};\n\n// An Identifier the parser emitted as a value reference (it carries a range,\n// which synthetic name nodes do not).\nexport const isIdentifierReference = (\n node: unknown,\n): node is ESTree.IdentifierReference =>\n isIdentifier(node) && Array.isArray(node.range);\n\nexport type ScopeContext = {\n sourceCode: { getScope: (node: ESTree.Node) => Scope };\n};\n\nconst resolveVariable = (\n context: ScopeContext,\n identifier: ESTree.IdentifierReference,\n): Variable | null => {\n let scope: Scope | null = context.sourceCode.getScope(identifier);\n while (scope !== null) {\n const variable = scope.set.get(identifier.name);\n if (variable !== undefined) {\n return variable;\n }\n scope = scope.upper;\n }\n return null;\n};\n\nconst isSingleAssignment = (variable: Variable): boolean =>\n variable.references.every(\n (reference) => !reference.isWrite() || reference.init,\n );\n\ntype ImportedBinding = { source: string; imported: string };\nconst NAMESPACE_IMPORT = \"*\";\n\nconst memberOfNamespace = (\n base: ImportedBinding | null,\n property: string | null,\n): ImportedBinding | null =>\n base?.imported === NAMESPACE_IMPORT && property !== null\n ? { source: base.source, imported: property }\n : null;\n\nconst bindingFromVariable = (\n context: ScopeContext,\n variable: Variable,\n seen: Set<unknown>,\n): ImportedBinding | null => {\n const definition = variable.defs.at(0);\n if (variable.defs.length !== 1 || definition === undefined) {\n return null;\n }\n const node: unknown = definition.node;\n if (definition.type === \"ImportBinding\") {\n const declaration: unknown = definition.parent;\n if (\n !isAstNode(node) ||\n node.importKind === \"type\" ||\n !isAstNode(declaration) ||\n declaration.type !== \"ImportDeclaration\" ||\n declaration.importKind === \"type\" ||\n !isStringLiteral(declaration.source)\n ) {\n return null;\n }\n const source = declaration.source.value;\n if (node.type === \"ImportNamespaceSpecifier\") {\n return { source, imported: NAMESPACE_IMPORT };\n }\n if (node.type === \"ImportDefaultSpecifier\") {\n return { source, imported: \"default\" };\n }\n const imported = getImportedName(node);\n return imported === null ? null : { source, imported };\n }\n if (\n definition.type !== \"Variable\" ||\n !isAstNode(node) ||\n node.type !== \"VariableDeclarator\" ||\n !isSingleAssignment(variable) ||\n !isIdentifier(node.id)\n ) {\n return null;\n }\n return resolveImportedExpressionForEnforcement(context, node.init, seen);\n};\n\n// The module export an expression evaluates to, or null for a local,\n// parameter, call result, or unresolved global.\nexport const resolveImportedExpressionForEnforcement = (\n context: ScopeContext,\n node: unknown,\n seen: Set<unknown> = new Set(),\n): ImportedBinding | null => {\n const expression = unwrapExpression(node);\n if (!isAstNode(expression) || seen.has(expression)) {\n return null;\n }\n seen.add(expression);\n if (isIdentifierReference(expression)) {\n const variable = resolveVariable(context, expression);\n return variable === null\n ? null\n : bindingFromVariable(context, variable, seen);\n }\n if (expression.type === \"MemberExpression\") {\n const base = resolveImportedExpressionForEnforcement(\n context,\n expression.object,\n seen,\n );\n return memberOfNamespace(base, memberPropertyName(expression));\n }\n return null;\n};\n"],"mappings":"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"}
|
package/dist/utils.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["// Shared AST helpers for the rules in this folder.\n//\n// Oxlint plugin AST nodes are passed in untyped. Each helper narrows from\n// `unknown` so rule files can call them without per-call type ceremony or\n// shared type-import boilerplate.\n\nimport type { ESTree, Ranged, Scope, Variable } from \"@oxlint/plugins\";\n\nexport type AstNode = Ranged & { type: string } & Record<string, unknown>;\n\ntype FilenameContext = {\n filename?: string;\n getFilename?: () => string;\n};\n\nexport const filenameForContext = (context: FilenameContext): string =>\n (context.filename ?? context.getFilename?.() ?? \"\").replaceAll(\"\\\\\", \"/\");\n\nexport const isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof (node as { type: unknown }).type === \"string\" &&\n \"range\" in node;\n\nexport const isIdentifier = (\n node: unknown,\n name?: string,\n): node is AstNode & { type: \"Identifier\"; name: string } => {\n if (!isAstNode(node) || node.type !== \"Identifier\") {\n return false;\n }\n if (typeof node.name !== \"string\") {\n return false;\n }\n return name === undefined || node.name === name;\n};\n\nexport const isStringLiteral = (\n node: unknown,\n): node is AstNode & { type: \"Literal\"; value: string } =>\n isAstNode(node) && node.type === \"Literal\" && typeof node.value === \"string\";\n\n// Resolve the static name of a Property or MemberExpression key:\n// Identifier.name or string-Literal.value. Returns null for computed keys\n// driven by a non-literal expression.\nexport const getPropertyName = (node: unknown): string | null => {\n if (isIdentifier(node)) {\n return node.name;\n }\n if (isStringLiteral(node)) {\n return node.value;\n }\n return null;\n};\n\nexport const staticStringValue = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (expression === null) return null;\n node = expression;\n if (isStringLiteral(node)) return node.value;\n if (\n !isAstNode(node) ||\n node.type !== \"TemplateLiteral\" ||\n !Array.isArray(node.expressions) ||\n node.expressions.length !== 0 ||\n !Array.isArray(node.quasis)\n )\n return null;\n const quasi = node.quasis.at(0);\n if (!isAstNode(quasi)) return null;\n const value = quasi.value;\n const cooked =\n typeof value === \"object\" && value !== null\n ? Reflect.get(value, \"cooked\")\n : undefined;\n return typeof cooked === \"string\" ? cooked : null;\n};\n\nexport const memberPropertyName = (node: unknown): string | null => {\n if (!isAstNode(node) || node.type !== \"MemberExpression\") {\n return null;\n }\n if (node.computed !== true) {\n return getPropertyName(node.property);\n }\n return staticStringValue(node.property);\n};\n\nexport const invokedCallee = (node: unknown): AstNode | null => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return null;\n }\n const callee = unwrapExpression(node.callee);\n if (!isAstNode(callee)) {\n return null;\n }\n if (\n callee.type === \"SequenceExpression\" &&\n Array.isArray(callee.expressions)\n ) {\n return unwrapExpression(callee.expressions.at(-1));\n }\n if (\n callee.type === \"MemberExpression\" &&\n (memberPropertyName(callee) === \"call\" ||\n memberPropertyName(callee) === \"apply\")\n ) {\n return unwrapExpression(callee.object);\n }\n return callee;\n};\n\n// Match `<object>.<property>` member access where both halves are\n// Identifiers and the access is not computed.\nexport const isMemberAccess = (\n node: unknown,\n object: string,\n property: string,\n): boolean =>\n isAstNode(node) &&\n node.type === \"MemberExpression\" &&\n node.computed === false &&\n isIdentifier(node.object, object) &&\n isIdentifier(node.property, property);\n\n// Match `CallExpression` whose callee is an Identifier with the given name.\nexport const isCallTo = (node: unknown, name: string): boolean =>\n isAstNode(node) &&\n node.type === \"CallExpression\" &&\n isIdentifier(node.callee, name);\n\n// Resolve the dot-notation name of a callee: an Identifier, or a\n// non-computed MemberExpression chain (e.g. `t.String`, `Schema.is`,\n// `process.stderr.write`). Returns null when the chain is computed\n// or the property name itself can't be resolved.\n//\n// If the chain is rooted at a non-Identifier expression (e.g. `foo().bar`),\n// returns the bare property name (\"bar\") rather than null. Callers that\n// match the result against a fixed allowlist must consider whether they\n// need to distinguish `foo().createSafeHandler` from `createSafeHandler`.\nexport const getCalleeName = (callee: unknown): string | null => {\n if (isIdentifier(callee)) {\n return callee.name;\n }\n if (!isAstNode(callee) || callee.type !== \"MemberExpression\") {\n return null;\n }\n if (callee.computed !== false) {\n return null;\n }\n const objectName = getCalleeName(callee.object);\n const propertyName = getPropertyName(callee.property);\n if (propertyName === null) {\n return null;\n }\n return objectName === null ? propertyName : `${objectName}.${propertyName}`;\n};\n\n// Parser wrappers with no value transformation of their own.\nexport const EXPRESSION_WRAPPER_TYPES = [\n \"ParenthesizedExpression\",\n \"ChainExpression\",\n \"TSNonNullExpression\",\n \"TSAsExpression\",\n \"TSSatisfiesExpression\",\n \"TSTypeAssertion\",\n \"TSInstantiationExpression\",\n] as const;\n\n// Keep the parser's other expression-bearing contexts intact.\nexport const unwrapExpression = (node: unknown): AstNode | null => {\n while (isAstNode(node)) {\n const { type } = node;\n if (\n !(EXPRESSION_WRAPPER_TYPES satisfies readonly ESTree.Node[\"type\"][]).some(\n (wrapper) => wrapper === type,\n )\n )\n return node;\n node = node.expression;\n }\n return null;\n};\n\nexport type LiteralArgumentResolution =\n | { status: \"known\"; args: unknown[] }\n | { status: \"opaque\"; args: unknown[]; spread: unknown; tail: unknown[] };\n\n// Expand literal array spreads and retain unresolved spread nodes in sequence.\n// Positional readers use only `args`, the prefix before the first barrier;\n// source inspectors can still inspect later entries in `tail`.\nexport const resolveLiteralArraySpreads = (\n args: unknown[],\n): LiteralArgumentResolution => {\n const expanded: unknown[] = [];\n const append = (argument: unknown): void => {\n if (!isAstNode(argument) || argument.type !== \"SpreadElement\") {\n expanded.push(argument);\n return;\n }\n const expression = unwrapExpression(argument.argument);\n if (\n expression?.type !== \"ArrayExpression\" ||\n !Array.isArray(expression.elements)\n ) {\n expanded.push(argument);\n return;\n }\n for (const element of expression.elements) append(element);\n };\n for (const argument of args) append(argument);\n const barrierIndex = expanded.findIndex(\n (argument) => isAstNode(argument) && argument.type === \"SpreadElement\",\n );\n if (barrierIndex === -1) return { status: \"known\", args: expanded };\n const spread = expanded.at(barrierIndex);\n return {\n status: \"opaque\",\n args: expanded.slice(0, barrierIndex),\n spread,\n tail: expanded.slice(barrierIndex + 1),\n };\n};\n\n// Resolve an ImportSpecifier's imported binding name (Identifier.name or\n// string-Literal.value). Returns null when the specifier shape is unexpected.\nexport const getImportedName = (specifier: unknown): string | null => {\n if (!isAstNode(specifier) || specifier.type !== \"ImportSpecifier\") {\n return null;\n }\n const imported = specifier.imported;\n if (isIdentifier(imported)) {\n return imported.name;\n }\n if (isStringLiteral(imported)) {\n return imported.value;\n }\n return null;\n};\n\n// Resolve an ImportSpecifier's local binding name. This differs from\n// getImportedName for aliased imports such as `import { source as local }`.\nexport const getImportLocalName = (specifier: unknown): string | null => {\n if (!isAstNode(specifier) || specifier.type !== \"ImportSpecifier\") {\n return null;\n }\n return isIdentifier(specifier.local) ? specifier.local.name : null;\n};\n\n// An Identifier the parser emitted as a value reference (it carries a range,\n// which synthetic name nodes do not).\nexport const isIdentifierReference = (\n node: unknown,\n): node is ESTree.IdentifierReference =>\n isIdentifier(node) && Array.isArray(node.range);\n\nexport type ScopeContext = {\n sourceCode: { getScope: (node: ESTree.Node) => Scope };\n};\n\nconst resolveVariable = (\n context: ScopeContext,\n identifier: ESTree.IdentifierReference,\n): Variable | null => {\n let scope: Scope | null = context.sourceCode.getScope(identifier);\n while (scope !== null) {\n const variable = scope.set.get(identifier.name);\n if (variable !== undefined) {\n return variable;\n }\n scope = scope.upper;\n }\n return null;\n};\n\nconst isSingleAssignment = (variable: Variable): boolean =>\n variable.references.every(\n (reference) => !reference.isWrite() || reference.init,\n );\n\ntype ImportedBinding = { source: string; imported: string };\nconst NAMESPACE_IMPORT = \"*\";\n\nconst memberOfNamespace = (\n base: ImportedBinding | null,\n property: string | null,\n): ImportedBinding | null =>\n base?.imported === NAMESPACE_IMPORT && property !== null\n ? { source: base.source, imported: property }\n : null;\n\nconst bindingFromVariable = (\n context: ScopeContext,\n variable: Variable,\n seen: Set<unknown>,\n): ImportedBinding | null => {\n const definition = variable.defs.at(0);\n if (variable.defs.length !== 1 || definition === undefined) {\n return null;\n }\n const node: unknown = definition.node;\n if (definition.type === \"ImportBinding\") {\n const declaration: unknown = definition.parent;\n if (\n !isAstNode(node) ||\n node.importKind === \"type\" ||\n !isAstNode(declaration) ||\n declaration.type !== \"ImportDeclaration\" ||\n declaration.importKind === \"type\" ||\n !isStringLiteral(declaration.source)\n ) {\n return null;\n }\n const source = declaration.source.value;\n if (node.type === \"ImportNamespaceSpecifier\") {\n return { source, imported: NAMESPACE_IMPORT };\n }\n if (node.type === \"ImportDefaultSpecifier\") {\n return { source, imported: \"default\" };\n }\n const imported = getImportedName(node);\n return imported === null ? null : { source, imported };\n }\n if (\n definition.type !== \"Variable\" ||\n !isAstNode(node) ||\n node.type !== \"VariableDeclarator\" ||\n !isSingleAssignment(variable) ||\n !isIdentifier(node.id)\n ) {\n return null;\n }\n return resolveImportedExpressionForEnforcement(context, node.init, seen);\n};\n\n// The module export an expression evaluates to, or null for a local,\n// parameter, call result, or unresolved global.\nexport const resolveImportedExpressionForEnforcement = (\n context: ScopeContext,\n node: unknown,\n seen: Set<unknown> = new Set(),\n): ImportedBinding | null => {\n const expression = unwrapExpression(node);\n if (!isAstNode(expression) || seen.has(expression)) {\n return null;\n }\n seen.add(expression);\n if (isIdentifierReference(expression)) {\n const variable = resolveVariable(context, expression);\n return variable === null\n ? null\n : bindingFromVariable(context, variable, seen);\n }\n if (expression.type === \"MemberExpression\") {\n const base = resolveImportedExpressionForEnforcement(\n context,\n expression.object,\n seen,\n );\n return memberOfNamespace(base, memberPropertyName(expression));\n }\n return null;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["// Shared AST helpers for the rules in this folder.\n//\n// Oxlint plugin AST nodes are passed in untyped. Each helper narrows from\n// `unknown` so rule files can call them without per-call type ceremony or\n// shared type-import boilerplate.\n\nimport type { ESTree, Ranged, Scope, Variable } from \"@oxlint/plugins\";\n\nexport type AstNode = Ranged & { type: string } & Record<string, unknown>;\n\ntype FilenameContext = {\n filename?: string;\n getFilename?: () => string;\n};\n\nexport const filenameForContext = (context: FilenameContext): string =>\n (context.filename ?? context.getFilename?.() ?? \"\").replaceAll(\"\\\\\", \"/\");\n\nexport const isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof node.type === \"string\" &&\n \"range\" in node;\n\nexport const isIdentifier = (\n node: unknown,\n name?: string,\n): node is AstNode & { type: \"Identifier\"; name: string } => {\n if (!isAstNode(node) || node.type !== \"Identifier\") {\n return false;\n }\n if (typeof node.name !== \"string\") {\n return false;\n }\n return name === undefined || node.name === name;\n};\n\nexport const isStringLiteral = (\n node: unknown,\n): node is AstNode & { type: \"Literal\"; value: string } =>\n isAstNode(node) && node.type === \"Literal\" && typeof node.value === \"string\";\n\n// Resolve the static name of a Property or MemberExpression key:\n// Identifier.name or string-Literal.value. Returns null for computed keys\n// driven by a non-literal expression.\nexport const getPropertyName = (node: unknown): string | null => {\n if (isIdentifier(node)) {\n return node.name;\n }\n if (isStringLiteral(node)) {\n return node.value;\n }\n return null;\n};\n\nexport const staticStringValue = (node: unknown): string | null => {\n const expression = unwrapExpression(node);\n if (expression === null) return null;\n node = expression;\n if (isStringLiteral(node)) return node.value;\n if (\n !isAstNode(node) ||\n node.type !== \"TemplateLiteral\" ||\n !Array.isArray(node.expressions) ||\n node.expressions.length !== 0 ||\n !Array.isArray(node.quasis)\n )\n return null;\n const quasi = node.quasis.at(0);\n if (!isAstNode(quasi)) return null;\n const value = quasi.value;\n const cooked =\n typeof value === \"object\" && value !== null\n ? Reflect.get(value, \"cooked\")\n : undefined;\n return typeof cooked === \"string\" ? cooked : null;\n};\n\nexport const memberPropertyName = (node: unknown): string | null => {\n if (!isAstNode(node) || node.type !== \"MemberExpression\") {\n return null;\n }\n if (node.computed !== true) {\n return getPropertyName(node.property);\n }\n return staticStringValue(node.property);\n};\n\nexport const invokedCallee = (node: unknown): AstNode | null => {\n if (!isAstNode(node) || node.type !== \"CallExpression\") {\n return null;\n }\n const callee = unwrapExpression(node.callee);\n if (!isAstNode(callee)) {\n return null;\n }\n if (\n callee.type === \"SequenceExpression\" &&\n Array.isArray(callee.expressions)\n ) {\n return unwrapExpression(callee.expressions.at(-1));\n }\n if (\n callee.type === \"MemberExpression\" &&\n (memberPropertyName(callee) === \"call\" ||\n memberPropertyName(callee) === \"apply\")\n ) {\n return unwrapExpression(callee.object);\n }\n return callee;\n};\n\n// Match `<object>.<property>` member access where both halves are\n// Identifiers and the access is not computed.\nexport const isMemberAccess = (\n node: unknown,\n object: string,\n property: string,\n): boolean =>\n isAstNode(node) &&\n node.type === \"MemberExpression\" &&\n node.computed === false &&\n isIdentifier(node.object, object) &&\n isIdentifier(node.property, property);\n\n// Match `CallExpression` whose callee is an Identifier with the given name.\nexport const isCallTo = (node: unknown, name: string): boolean =>\n isAstNode(node) &&\n node.type === \"CallExpression\" &&\n isIdentifier(node.callee, name);\n\n// Resolve the dot-notation name of a callee: an Identifier, or a\n// non-computed MemberExpression chain (e.g. `t.String`, `Schema.is`,\n// `process.stderr.write`). Returns null when the chain is computed\n// or the property name itself can't be resolved.\n//\n// If the chain is rooted at a non-Identifier expression (e.g. `foo().bar`),\n// returns the bare property name (\"bar\") rather than null. Callers that\n// match the result against a fixed allowlist must consider whether they\n// need to distinguish `foo().createSafeHandler` from `createSafeHandler`.\nexport const getCalleeName = (callee: unknown): string | null => {\n if (isIdentifier(callee)) {\n return callee.name;\n }\n if (!isAstNode(callee) || callee.type !== \"MemberExpression\") {\n return null;\n }\n if (callee.computed !== false) {\n return null;\n }\n const objectName = getCalleeName(callee.object);\n const propertyName = getPropertyName(callee.property);\n if (propertyName === null) {\n return null;\n }\n return objectName === null ? propertyName : `${objectName}.${propertyName}`;\n};\n\n// Parser wrappers with no value transformation of their own.\nexport const EXPRESSION_WRAPPER_TYPES = [\n \"ParenthesizedExpression\",\n \"ChainExpression\",\n \"TSNonNullExpression\",\n \"TSAsExpression\",\n \"TSSatisfiesExpression\",\n \"TSTypeAssertion\",\n \"TSInstantiationExpression\",\n] as const;\n\n// Keep the parser's other expression-bearing contexts intact.\nexport const unwrapExpression = (node: unknown): AstNode | null => {\n while (isAstNode(node)) {\n const { type } = node;\n if (\n !(EXPRESSION_WRAPPER_TYPES satisfies readonly ESTree.Node[\"type\"][]).some(\n (wrapper) => wrapper === type,\n )\n )\n return node;\n node = node.expression;\n }\n return null;\n};\n\nexport type LiteralArgumentResolution =\n | { status: \"known\"; args: unknown[] }\n | { status: \"opaque\"; args: unknown[]; spread: unknown; tail: unknown[] };\n\n// Expand literal array spreads and retain unresolved spread nodes in sequence.\n// Positional readers use only `args`, the prefix before the first barrier;\n// source inspectors can still inspect later entries in `tail`.\nexport const resolveLiteralArraySpreads = (\n args: unknown[],\n): LiteralArgumentResolution => {\n const expanded: unknown[] = [];\n const append = (argument: unknown): void => {\n if (!isAstNode(argument) || argument.type !== \"SpreadElement\") {\n expanded.push(argument);\n return;\n }\n const expression = unwrapExpression(argument.argument);\n if (\n expression?.type !== \"ArrayExpression\" ||\n !Array.isArray(expression.elements)\n ) {\n expanded.push(argument);\n return;\n }\n for (const element of expression.elements) append(element);\n };\n for (const argument of args) append(argument);\n const barrierIndex = expanded.findIndex(\n (argument) => isAstNode(argument) && argument.type === \"SpreadElement\",\n );\n if (barrierIndex === -1) return { status: \"known\", args: expanded };\n const spread = expanded.at(barrierIndex);\n return {\n status: \"opaque\",\n args: expanded.slice(0, barrierIndex),\n spread,\n tail: expanded.slice(barrierIndex + 1),\n };\n};\n\n// Resolve an ImportSpecifier's imported binding name (Identifier.name or\n// string-Literal.value). Returns null when the specifier shape is unexpected.\nexport const getImportedName = (specifier: unknown): string | null => {\n if (!isAstNode(specifier) || specifier.type !== \"ImportSpecifier\") {\n return null;\n }\n const imported = specifier.imported;\n if (isIdentifier(imported)) {\n return imported.name;\n }\n if (isStringLiteral(imported)) {\n return imported.value;\n }\n return null;\n};\n\n// Resolve an ImportSpecifier's local binding name. This differs from\n// getImportedName for aliased imports such as `import { source as local }`.\nexport const getImportLocalName = (specifier: unknown): string | null => {\n if (!isAstNode(specifier) || specifier.type !== \"ImportSpecifier\") {\n return null;\n }\n return isIdentifier(specifier.local) ? specifier.local.name : null;\n};\n\n// An Identifier the parser emitted as a value reference (it carries a range,\n// which synthetic name nodes do not).\nexport const isIdentifierReference = (\n node: unknown,\n): node is ESTree.IdentifierReference =>\n isIdentifier(node) && Array.isArray(node.range);\n\nexport type ScopeContext = {\n sourceCode: { getScope: (node: ESTree.Node) => Scope };\n};\n\nconst resolveVariable = (\n context: ScopeContext,\n identifier: ESTree.IdentifierReference,\n): Variable | null => {\n let scope: Scope | null = context.sourceCode.getScope(identifier);\n while (scope !== null) {\n const variable = scope.set.get(identifier.name);\n if (variable !== undefined) {\n return variable;\n }\n scope = scope.upper;\n }\n return null;\n};\n\nconst isSingleAssignment = (variable: Variable): boolean =>\n variable.references.every(\n (reference) => !reference.isWrite() || reference.init,\n );\n\ntype ImportedBinding = { source: string; imported: string };\nconst NAMESPACE_IMPORT = \"*\";\n\nconst memberOfNamespace = (\n base: ImportedBinding | null,\n property: string | null,\n): ImportedBinding | null =>\n base?.imported === NAMESPACE_IMPORT && property !== null\n ? { source: base.source, imported: property }\n : null;\n\nconst bindingFromVariable = (\n context: ScopeContext,\n variable: Variable,\n seen: Set<unknown>,\n): ImportedBinding | null => {\n const definition = variable.defs.at(0);\n if (variable.defs.length !== 1 || definition === undefined) {\n return null;\n }\n const node: unknown = definition.node;\n if (definition.type === \"ImportBinding\") {\n const declaration: unknown = definition.parent;\n if (\n !isAstNode(node) ||\n node.importKind === \"type\" ||\n !isAstNode(declaration) ||\n declaration.type !== \"ImportDeclaration\" ||\n declaration.importKind === \"type\" ||\n !isStringLiteral(declaration.source)\n ) {\n return null;\n }\n const source = declaration.source.value;\n if (node.type === \"ImportNamespaceSpecifier\") {\n return { source, imported: NAMESPACE_IMPORT };\n }\n if (node.type === \"ImportDefaultSpecifier\") {\n return { source, imported: \"default\" };\n }\n const imported = getImportedName(node);\n return imported === null ? null : { source, imported };\n }\n if (\n definition.type !== \"Variable\" ||\n !isAstNode(node) ||\n node.type !== \"VariableDeclarator\" ||\n !isSingleAssignment(variable) ||\n !isIdentifier(node.id)\n ) {\n return null;\n }\n return resolveImportedExpressionForEnforcement(context, node.init, seen);\n};\n\n// The module export an expression evaluates to, or null for a local,\n// parameter, call result, or unresolved global.\nexport const resolveImportedExpressionForEnforcement = (\n context: ScopeContext,\n node: unknown,\n seen: Set<unknown> = new Set(),\n): ImportedBinding | null => {\n const expression = unwrapExpression(node);\n if (!isAstNode(expression) || seen.has(expression)) {\n return null;\n }\n seen.add(expression);\n if (isIdentifierReference(expression)) {\n const variable = resolveVariable(context, expression);\n return variable === null\n ? null\n : bindingFromVariable(context, variable, seen);\n }\n if (expression.type === \"MemberExpression\") {\n const base = resolveImportedExpressionForEnforcement(\n context,\n expression.object,\n seen,\n );\n return memberOfNamespace(base, memberPropertyName(expression));\n }\n return null;\n};\n"],"mappings":"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"}
|