869 lines
23 KiB
TypeScript
869 lines
23 KiB
TypeScript
import babelParser from "@babel/eslint-parser";
|
|
import eslint from "@eslint/js";
|
|
import reactHooks from "eslint-plugin-react-hooks";
|
|
import globals from "globals";
|
|
import type { Rule } from "eslint";
|
|
|
|
const sourceExtensions = "{ts,tsx,mts,cts}";
|
|
|
|
const layerPatterns = {
|
|
domain: [
|
|
"**/application/**",
|
|
"**/presentation/**",
|
|
"**/adapters/**",
|
|
"**/bootstrap/**",
|
|
"react",
|
|
"react-dom",
|
|
"@tanstack/**",
|
|
],
|
|
application: [
|
|
"**/presentation/**",
|
|
"**/adapters/**",
|
|
"**/bootstrap/**",
|
|
"react",
|
|
"react-dom",
|
|
"@tanstack/**",
|
|
],
|
|
presentation: [
|
|
"**/adapters/**",
|
|
"**/bootstrap/**",
|
|
"**/application/ports/out/**",
|
|
"@tanstack/**",
|
|
],
|
|
adapters: ["**/presentation/**", "**/bootstrap/**"],
|
|
};
|
|
|
|
function restrictedImports(patterns: readonly string[]) {
|
|
return ["error", { patterns }];
|
|
}
|
|
|
|
const restrictedBrowserDataProperties = [
|
|
"error",
|
|
...(["globalThis", "window", "self"] as const).flatMap((object) =>
|
|
[
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"indexedDB",
|
|
"caches",
|
|
"navigator",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
"URL",
|
|
].map((property) => ({
|
|
object,
|
|
property,
|
|
message:
|
|
"Browser file/storage APIs are adapter-owned; use the policy port.",
|
|
})),
|
|
),
|
|
{
|
|
object: "navigator",
|
|
property: "storage",
|
|
message: "StorageManager and OPFS access are adapter-owned.",
|
|
},
|
|
{
|
|
object: "URL",
|
|
property: "createObjectURL",
|
|
message: "Object URL allocation is owned by the preview/download adapter.",
|
|
},
|
|
{
|
|
object: "URL",
|
|
property: "revokeObjectURL",
|
|
message: "Object URL revocation is owned by the preview/download adapter.",
|
|
},
|
|
] as const;
|
|
|
|
const browserCapabilityRoots = new Set([
|
|
"globalThis",
|
|
"window",
|
|
"self",
|
|
"navigator",
|
|
"URL",
|
|
]);
|
|
const browserCapabilityProperties = new Set([
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"indexedDB",
|
|
"caches",
|
|
"navigator",
|
|
"storage",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
"URL",
|
|
"createObjectURL",
|
|
"revokeObjectURL",
|
|
]);
|
|
|
|
/**
|
|
* no-restricted-properties only recognizes the literal object identifier.
|
|
* This rule follows local aliases of browser roots so `const host =
|
|
* globalThis; host.indexedDB` cannot bypass the owned-adapter boundary.
|
|
*/
|
|
const noBrowserCapabilityAliasRule: Rule.RuleModule = {
|
|
meta: {
|
|
type: "problem",
|
|
schema: [],
|
|
messages: {
|
|
owned:
|
|
"Browser file/storage capabilities are owned by the approved common adapters.",
|
|
},
|
|
},
|
|
create(context) {
|
|
const aliases = new Set<string>();
|
|
|
|
const unwrap = (input: any): any => {
|
|
let node = input;
|
|
while (
|
|
node &&
|
|
[
|
|
"ChainExpression",
|
|
"TSAsExpression",
|
|
"TSNonNullExpression",
|
|
"TSTypeAssertion",
|
|
].includes(node.type)
|
|
) {
|
|
node = node.expression;
|
|
}
|
|
return node;
|
|
};
|
|
|
|
const propertyName = (input: any): string | null => {
|
|
const node = unwrap(input);
|
|
if (!node) return null;
|
|
if (!node.computed && node.property?.type === "Identifier") {
|
|
return node.property.name;
|
|
}
|
|
if (
|
|
node.computed &&
|
|
node.property?.type === "Literal" &&
|
|
typeof node.property.value === "string"
|
|
) {
|
|
return node.property.value;
|
|
}
|
|
if (
|
|
node.computed &&
|
|
node.property?.type === "StringLiteral"
|
|
) {
|
|
return node.property.value;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const isRootAlias = (input: any): boolean => {
|
|
const node = unwrap(input);
|
|
if (!node) return false;
|
|
if (node.type === "Identifier") {
|
|
return browserCapabilityRoots.has(node.name) ||
|
|
aliases.has(node.name);
|
|
}
|
|
if (node.type === "MemberExpression") {
|
|
return isRootAlias(node.object);
|
|
}
|
|
if (node.type === "LogicalExpression") {
|
|
return isRootAlias(node.left) || isRootAlias(node.right);
|
|
}
|
|
if (node.type === "ConditionalExpression") {
|
|
return (
|
|
isRootAlias(node.consequent) ||
|
|
isRootAlias(node.alternate)
|
|
);
|
|
}
|
|
if (node.type === "SequenceExpression") {
|
|
return node.expressions.some(isRootAlias);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const isBareRootAlias = (input: any): boolean => {
|
|
const node = unwrap(input);
|
|
if (!node) return false;
|
|
if (node.type === "Identifier") {
|
|
return browserCapabilityRoots.has(node.name) ||
|
|
aliases.has(node.name);
|
|
}
|
|
if (node.type === "LogicalExpression") {
|
|
return (
|
|
isBareRootAlias(node.left) ||
|
|
isBareRootAlias(node.right)
|
|
);
|
|
}
|
|
if (node.type === "ConditionalExpression") {
|
|
return (
|
|
isBareRootAlias(node.consequent) ||
|
|
isBareRootAlias(node.alternate)
|
|
);
|
|
}
|
|
if (node.type === "SequenceExpression") {
|
|
return node.expressions.some(isBareRootAlias);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const isDirectBrowserRoot = (input: any): boolean => {
|
|
const node = unwrap(input);
|
|
return (
|
|
node?.type === "Identifier" &&
|
|
browserCapabilityRoots.has(node.name)
|
|
);
|
|
};
|
|
|
|
const safeMethodThisBinding = (
|
|
call: any,
|
|
argumentIndex: number,
|
|
): boolean => {
|
|
if (argumentIndex !== 0) return false;
|
|
const callee = unwrap(call.callee);
|
|
if (
|
|
callee?.type !== "MemberExpression" ||
|
|
propertyName(callee) !== "bind"
|
|
) {
|
|
return false;
|
|
}
|
|
const target = unwrap(callee.object);
|
|
const targetName =
|
|
target?.type === "MemberExpression"
|
|
? propertyName(target)
|
|
: null;
|
|
return (
|
|
targetName !== null &&
|
|
!browserCapabilityProperties.has(targetName) &&
|
|
isRootAlias(target.object)
|
|
);
|
|
};
|
|
|
|
const rememberPattern = (patternInput: any, source: any): void => {
|
|
const pattern = unwrap(patternInput);
|
|
if (!pattern || !isRootAlias(source)) return;
|
|
if (pattern.type === "Identifier") {
|
|
aliases.add(pattern.name);
|
|
return;
|
|
}
|
|
if (pattern.type !== "ObjectPattern") return;
|
|
for (const property of pattern.properties ?? []) {
|
|
if (property.type !== "Property") continue;
|
|
const name =
|
|
property.computed
|
|
? property.key?.value
|
|
: property.key?.name ?? property.key?.value;
|
|
if (
|
|
typeof name === "string" &&
|
|
browserCapabilityProperties.has(name)
|
|
) {
|
|
context.report({ node: property, messageId: "owned" });
|
|
}
|
|
}
|
|
};
|
|
|
|
return {
|
|
VariableDeclarator(node: any) {
|
|
rememberPattern(node.id, node.init);
|
|
},
|
|
AssignmentExpression(node: any) {
|
|
rememberPattern(node.left, node.right);
|
|
const target = unwrap(node.left);
|
|
if (
|
|
target?.type !== "Identifier" &&
|
|
target?.type !== "ObjectPattern" &&
|
|
isBareRootAlias(node.right)
|
|
) {
|
|
context.report({ node: node.right, messageId: "owned" });
|
|
}
|
|
},
|
|
AssignmentPattern(node: any) {
|
|
rememberPattern(node.left, node.right);
|
|
},
|
|
PropertyDefinition(node: any) {
|
|
if (isBareRootAlias(node.value)) {
|
|
context.report({ node: node.value, messageId: "owned" });
|
|
}
|
|
},
|
|
MemberExpression(node: any) {
|
|
const name = propertyName(node);
|
|
if (
|
|
isRootAlias(node.object) &&
|
|
((name !== null &&
|
|
browserCapabilityProperties.has(name)) ||
|
|
(name === null && node.computed))
|
|
) {
|
|
context.report({ node, messageId: "owned" });
|
|
}
|
|
},
|
|
CallExpression(node: any) {
|
|
for (const [index, argument] of (
|
|
node.arguments ?? []
|
|
).entries()) {
|
|
const candidate =
|
|
argument.type === "SpreadElement"
|
|
? argument.argument
|
|
: argument;
|
|
if (
|
|
isDirectBrowserRoot(candidate) &&
|
|
!safeMethodThisBinding(node, index)
|
|
) {
|
|
context.report({ node: argument, messageId: "owned" });
|
|
}
|
|
}
|
|
const callee = unwrap(node.callee);
|
|
if (
|
|
callee?.type !== "MemberExpression" ||
|
|
propertyName(callee) !== "get" ||
|
|
unwrap(callee.object)?.type !== "Identifier" ||
|
|
unwrap(callee.object).name !== "Reflect" ||
|
|
!isRootAlias(node.arguments?.[0])
|
|
) {
|
|
return;
|
|
}
|
|
const key = unwrap(node.arguments?.[1]);
|
|
if (
|
|
(key?.type === "Literal" ||
|
|
key?.type === "StringLiteral") &&
|
|
typeof key.value === "string" &&
|
|
browserCapabilityProperties.has(key.value)
|
|
) {
|
|
context.report({ node, messageId: "owned" });
|
|
}
|
|
},
|
|
NewExpression(node: any) {
|
|
for (const argument of node.arguments ?? []) {
|
|
const candidate =
|
|
argument.type === "SpreadElement"
|
|
? argument.argument
|
|
: argument;
|
|
if (isBareRootAlias(candidate)) {
|
|
context.report({ node: argument, messageId: "owned" });
|
|
}
|
|
}
|
|
},
|
|
Property(node: any) {
|
|
if (
|
|
node.parent?.type === "ObjectExpression" &&
|
|
isBareRootAlias(node.value)
|
|
) {
|
|
context.report({ node: node.value, messageId: "owned" });
|
|
}
|
|
},
|
|
ArrayExpression(node: any) {
|
|
for (const element of node.elements ?? []) {
|
|
if (isBareRootAlias(element)) {
|
|
context.report({ node: element, messageId: "owned" });
|
|
}
|
|
}
|
|
},
|
|
ReturnStatement(node: any) {
|
|
if (isBareRootAlias(node.argument)) {
|
|
context.report({ node: node.argument, messageId: "owned" });
|
|
}
|
|
},
|
|
ArrowFunctionExpression(node: any) {
|
|
if (
|
|
node.expression === true &&
|
|
isBareRootAlias(node.body)
|
|
) {
|
|
context.report({ node: node.body, messageId: "owned" });
|
|
}
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
const browserDataBoundaryPlugin = {
|
|
rules: {
|
|
"no-capability-alias": noBrowserCapabilityAliasRule,
|
|
},
|
|
};
|
|
|
|
const commonLanguageOptions = {
|
|
ecmaVersion: "latest",
|
|
sourceType: "module",
|
|
globals: {
|
|
...globals.browser,
|
|
...globals.node,
|
|
},
|
|
};
|
|
|
|
const commonSecurityRules = {
|
|
"no-eval": "error",
|
|
"no-new-func": "error",
|
|
"no-script-url": "error",
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
selector: "JSXAttribute[name.name='dangerouslySetInnerHTML']",
|
|
message: "Raw HTML injection is prohibited by FE-OC-019.",
|
|
},
|
|
{
|
|
selector:
|
|
"CallExpression[callee.object.name='document'][callee.property.name='createElement'][arguments.0.value='script']",
|
|
message: "Runtime script construction is prohibited by FE-OC-019.",
|
|
},
|
|
],
|
|
};
|
|
|
|
const hookRules = {
|
|
"react-hooks/rules-of-hooks": "error",
|
|
"react-hooks/exhaustive-deps": "error",
|
|
};
|
|
|
|
export default [
|
|
{
|
|
ignores: [
|
|
"dist/**",
|
|
"node_modules/**",
|
|
"artifacts/**",
|
|
"tests/fixtures/typecheck/**",
|
|
"tests/fixtures/architecture/forbidden/**",
|
|
"tests/fixtures/diagnostics/forbidden/**",
|
|
"tests/fixtures/i18n/forbidden/**",
|
|
"tests/fixtures/security/forbidden/**",
|
|
"tests/fixtures/optional-recipes/**",
|
|
"tests/fixtures/browser-file-storage-boundaries/**",
|
|
],
|
|
},
|
|
eslint.configs.recommended,
|
|
{
|
|
files: [`**/*.${sourceExtensions}`],
|
|
languageOptions: {
|
|
...commonLanguageOptions,
|
|
parserOptions: {
|
|
ecmaFeatures: { jsx: true },
|
|
},
|
|
},
|
|
plugins: {
|
|
"react-hooks": reactHooks,
|
|
},
|
|
rules: {
|
|
...commonSecurityRules,
|
|
...hookRules,
|
|
},
|
|
},
|
|
{
|
|
files: ["**/*.{ts,mts,cts}"],
|
|
languageOptions: {
|
|
...commonLanguageOptions,
|
|
parser: babelParser,
|
|
parserOptions: {
|
|
requireConfigFile: false,
|
|
babelOptions: {
|
|
plugins: [
|
|
["@babel/plugin-syntax-typescript", { isTSX: false }],
|
|
],
|
|
},
|
|
},
|
|
},
|
|
rules: {
|
|
"no-undef": "off",
|
|
"no-unused-vars": "off",
|
|
},
|
|
},
|
|
{
|
|
files: ["**/*.d.ts"],
|
|
languageOptions: {
|
|
...commonLanguageOptions,
|
|
parser: babelParser,
|
|
parserOptions: {
|
|
requireConfigFile: false,
|
|
babelOptions: {
|
|
plugins: [
|
|
["@babel/plugin-syntax-typescript", { dts: true }],
|
|
],
|
|
},
|
|
},
|
|
},
|
|
rules: {
|
|
"no-undef": "off",
|
|
"no-unused-vars": "off",
|
|
},
|
|
},
|
|
{
|
|
files: ["**/*.tsx"],
|
|
languageOptions: {
|
|
...commonLanguageOptions,
|
|
parser: babelParser,
|
|
parserOptions: {
|
|
requireConfigFile: false,
|
|
babelOptions: {
|
|
plugins: [
|
|
[
|
|
"@babel/plugin-syntax-typescript",
|
|
{ allExtensions: true, isTSX: true },
|
|
],
|
|
"@babel/plugin-syntax-jsx",
|
|
],
|
|
},
|
|
},
|
|
},
|
|
rules: {
|
|
"no-undef": "off",
|
|
"no-unused-vars": "off",
|
|
},
|
|
},
|
|
{
|
|
files: [
|
|
`src/domain/**/*.${sourceExtensions}`,
|
|
`src/application/**/*.${sourceExtensions}`,
|
|
`src/presentation/**/*.${sourceExtensions}`,
|
|
`src/bootstrap/**/*.${sourceExtensions}`,
|
|
`src/features/**/*.${sourceExtensions}`,
|
|
`src/adapters/**/*.${sourceExtensions}`,
|
|
],
|
|
ignores: [
|
|
`src/adapters/browser-file-storage/**/*.${sourceExtensions}`,
|
|
`src/adapters/browser-files/**/*.${sourceExtensions}`,
|
|
`src/adapters/browser-transfer/**/*.${sourceExtensions}`,
|
|
`src/adapters/cache-storage/**/*.${sourceExtensions}`,
|
|
`src/adapters/cross-context-invalidation/**/*.${sourceExtensions}`,
|
|
`src/adapters/service-worker/**/*.${sourceExtensions}`,
|
|
`src/bootstrap/register-service-worker.ts`,
|
|
`src/adapters/platform/browser-lifecycle.ts`,
|
|
`src/adapters/storage/**/*.${sourceExtensions}`,
|
|
`src/adapters/storage/indexeddb/**/*.${sourceExtensions}`,
|
|
`src/adapters/storage/opfs/**/*.${sourceExtensions}`,
|
|
],
|
|
plugins: {
|
|
"browser-data-boundary": browserDataBoundaryPlugin,
|
|
},
|
|
rules: {
|
|
"browser-data-boundary/no-capability-alias": "error",
|
|
},
|
|
},
|
|
{
|
|
files: [`src/domain/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports(layerPatterns.domain),
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"window",
|
|
"document",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"fetch",
|
|
"indexedDB",
|
|
"caches",
|
|
"navigator",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [`src/application/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports(layerPatterns.application),
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"window",
|
|
"document",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"fetch",
|
|
"indexedDB",
|
|
"caches",
|
|
"navigator",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [`src/presentation/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports(layerPatterns.presentation),
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"fetch",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"indexedDB",
|
|
"caches",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [
|
|
`src/presentation/adapters/query/**/*.${sourceExtensions}`,
|
|
],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports([
|
|
"**/adapters/http/**",
|
|
"**/adapters/storage/**",
|
|
"**/adapters/auth/**",
|
|
"**/bootstrap/**",
|
|
"**/application/ports/out/**",
|
|
]),
|
|
},
|
|
},
|
|
{
|
|
files: [`src/presentation/templates/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports([
|
|
"**/application/**",
|
|
"**/adapters/**",
|
|
"**/bootstrap/**",
|
|
"@tanstack/**",
|
|
]),
|
|
},
|
|
},
|
|
{
|
|
files: [
|
|
`src/bootstrap/**/*.${sourceExtensions}`,
|
|
`src/features/installed-feature-*.${sourceExtensions}`,
|
|
],
|
|
rules: {
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"indexedDB",
|
|
"caches",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [`src/adapters/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports(layerPatterns.adapters),
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"indexedDB",
|
|
"caches",
|
|
"navigator",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [
|
|
`src/adapters/browser-file-storage/**/*.${sourceExtensions}`,
|
|
`src/adapters/browser-files/**/*.${sourceExtensions}`,
|
|
`src/adapters/browser-transfer/**/*.${sourceExtensions}`,
|
|
`src/adapters/cache-storage/**/*.${sourceExtensions}`,
|
|
`src/adapters/cross-context-invalidation/**/*.${sourceExtensions}`,
|
|
`src/adapters/service-worker/**/*.${sourceExtensions}`,
|
|
`src/bootstrap/register-service-worker.ts`,
|
|
`src/adapters/platform/browser-lifecycle.ts`,
|
|
`src/adapters/storage/**/*.${sourceExtensions}`,
|
|
`src/adapters/storage/indexeddb/**/*.${sourceExtensions}`,
|
|
`src/adapters/storage/opfs/**/*.${sourceExtensions}`,
|
|
],
|
|
rules: {
|
|
"no-restricted-properties": "off",
|
|
"no-restricted-globals": "off",
|
|
},
|
|
},
|
|
{
|
|
files: [`src/features/*/domain/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports(layerPatterns.domain),
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"window",
|
|
"document",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"fetch",
|
|
"indexedDB",
|
|
"caches",
|
|
"navigator",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [`src/features/*/application/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports(layerPatterns.application),
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"window",
|
|
"document",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"fetch",
|
|
"indexedDB",
|
|
"caches",
|
|
"navigator",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [`src/features/*/presentation/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports([
|
|
"**/features/*/adapters/**",
|
|
"**/adapters/http/**",
|
|
"**/adapters/storage/**",
|
|
"**/adapters/auth/**",
|
|
"**/bootstrap/**",
|
|
"**/application/ports/out/**",
|
|
"@tanstack/**",
|
|
]),
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"fetch",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"indexedDB",
|
|
"caches",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [`src/features/*/adapters/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports([
|
|
"**/presentation/**",
|
|
"**/bootstrap/**",
|
|
"@tanstack/**",
|
|
]),
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"indexedDB",
|
|
"caches",
|
|
"navigator",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [`tests/**/*.${sourceExtensions}`],
|
|
languageOptions: {
|
|
globals: {
|
|
...globals.browser,
|
|
...globals.node,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
files: [`tests/support/browser/**/*.${sourceExtensions}`],
|
|
rules: {
|
|
"react-hooks/rules-of-hooks": "off",
|
|
},
|
|
},
|
|
{
|
|
files: [
|
|
`tests/fixtures/architecture/forbidden/**/*.${sourceExtensions}`,
|
|
],
|
|
rules: {
|
|
"no-restricted-imports": restrictedImports([
|
|
"**/adapters/**",
|
|
"**/application/ports/out/**",
|
|
"@tanstack/**",
|
|
"react",
|
|
"react-dom",
|
|
"**/application/**",
|
|
]),
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"fetch",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: [
|
|
`tests/fixtures/browser-file-storage-boundaries/forbidden/**/*.${sourceExtensions}`,
|
|
],
|
|
plugins: {
|
|
"browser-data-boundary": browserDataBoundaryPlugin,
|
|
},
|
|
rules: {
|
|
"browser-data-boundary/no-capability-alias": "error",
|
|
"no-restricted-properties": restrictedBrowserDataProperties,
|
|
"no-restricted-globals": [
|
|
"error",
|
|
"BroadcastChannel",
|
|
"localStorage",
|
|
"sessionStorage",
|
|
"indexedDB",
|
|
"caches",
|
|
"File",
|
|
"Blob",
|
|
"FileSystemFileHandle",
|
|
"FileSystemDirectoryHandle",
|
|
"showOpenFilePicker",
|
|
"showSaveFilePicker",
|
|
],
|
|
},
|
|
},
|
|
];
|