@akanjs/lint 0.0.53 → 0.0.55
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/index.mjs +15 -0
- package/jest.config.mjs +12 -0
- package/package.json +7 -1
- package/src/rules/noImportClientFunctions.mjs +35 -0
- package/src/rules/noImportExternalLibrary.mjs +41 -0
- package/src/rules/nonScalarPropsRestricted.mjs +41 -0
- package/src/rules/useClientByFile.mjs +52 -0
- package/src/util/fileHasClientFunction.mjs +24 -0
- package/src/util/fileHasContent.mjs +7 -0
- package/src/util/fileHasUseClient.mjs +7 -0
- package/src/util/fileIsPureImportFile.mjs +40 -0
- package/src/util/fileIsServerFile.mjs +37 -0
- package/src/util/getAppName.mjs +8 -0
- package/src/util/getFilename.mjs +7 -0
- package/src/util/index.mjs +18 -0
- package/src/util/isInternalImport.mjs +25 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { noImportClientFunctions } from "./src/rules/noImportClientFunctions";
|
|
2
|
+
import { noImportExternalLibrary } from "./src/rules/noImportExternalLibrary";
|
|
3
|
+
import { nonScalarPropsRestricted } from "./src/rules/nonScalarPropsRestricted";
|
|
4
|
+
import { useClientByFile } from "./src/rules/useClientByFile";
|
|
5
|
+
var lint_default = {
|
|
6
|
+
rules: {
|
|
7
|
+
noImportExternalLibrary,
|
|
8
|
+
noImportClientFunctions,
|
|
9
|
+
nonScalarPropsRestricted,
|
|
10
|
+
useClientByFile
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
lint_default as default
|
|
15
|
+
};
|
package/jest.config.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
var jest_config_default = {
|
|
2
|
+
displayName: "eslint-rules",
|
|
3
|
+
// preset: "../../pkgs/@akanjs/config/src/jest.preset.js",
|
|
4
|
+
transform: {
|
|
5
|
+
"^.+\\.[tj]s$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }]
|
|
6
|
+
},
|
|
7
|
+
moduleFileExtensions: ["ts", "js", "html"],
|
|
8
|
+
coverageDirectory: "../../../coverage/pkgs/@akanjs/lint"
|
|
9
|
+
};
|
|
10
|
+
export {
|
|
11
|
+
jest_config_default as default
|
|
12
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/lint",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.55",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,5 +16,11 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@typescript-eslint/utils": "^8.29.1"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"require": "./index.js",
|
|
23
|
+
"import": "./index.mjs"
|
|
24
|
+
}
|
|
19
25
|
}
|
|
20
26
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { fileHasClientFunction, fileIsServerFile } from "../util";
|
|
3
|
+
const noImportClientFunctions = ESLintUtils.RuleCreator(() => __filename)({
|
|
4
|
+
name: "noImportClientFunctions",
|
|
5
|
+
meta: {
|
|
6
|
+
type: "problem",
|
|
7
|
+
docs: {
|
|
8
|
+
description: "Enforce that client functions are not imported."
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
noImportClientFunctions: "Error: Client functions should not be imported."
|
|
12
|
+
},
|
|
13
|
+
// fixable: "code",
|
|
14
|
+
schema: []
|
|
15
|
+
},
|
|
16
|
+
defaultOptions: [],
|
|
17
|
+
create(context) {
|
|
18
|
+
const isServerFile = fileIsServerFile(context.filename, context.sourceCode.text);
|
|
19
|
+
if (!isServerFile)
|
|
20
|
+
return {};
|
|
21
|
+
return {
|
|
22
|
+
ImportDeclaration(node) {
|
|
23
|
+
const hasClientFunction = fileHasClientFunction(node);
|
|
24
|
+
if (hasClientFunction)
|
|
25
|
+
context.report({
|
|
26
|
+
node,
|
|
27
|
+
messageId: "noImportClientFunctions"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
export {
|
|
34
|
+
noImportClientFunctions
|
|
35
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { fileIsPureImportFile, getAppName, isInternalImport } from "../util";
|
|
3
|
+
const noImportExternalLibrary = ESLintUtils.RuleCreator(() => __filename)({
|
|
4
|
+
name: "noImportExternalLibrary",
|
|
5
|
+
meta: {
|
|
6
|
+
type: "problem",
|
|
7
|
+
docs: {
|
|
8
|
+
description: "Enforce that external libraries are not imported."
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
noImportExternalLibrary: "Error: External libraries should not be imported."
|
|
12
|
+
},
|
|
13
|
+
// fixable: "code",
|
|
14
|
+
schema: []
|
|
15
|
+
},
|
|
16
|
+
defaultOptions: [],
|
|
17
|
+
create(context) {
|
|
18
|
+
const isPureImportFile = fileIsPureImportFile(context.filename);
|
|
19
|
+
if (!isPureImportFile)
|
|
20
|
+
return {};
|
|
21
|
+
const filePaths = context.filename.split("/");
|
|
22
|
+
const appName = getAppName(filePaths);
|
|
23
|
+
const appPath = appName ? `@${appName}` : null;
|
|
24
|
+
return {
|
|
25
|
+
ImportDeclaration(node) {
|
|
26
|
+
const importPaths = node.source.value.split("/");
|
|
27
|
+
if (!isInternalImport(importPaths, appPath))
|
|
28
|
+
context.report({
|
|
29
|
+
node,
|
|
30
|
+
messageId: "noImportExternalLibrary"
|
|
31
|
+
// fix(fixer) {
|
|
32
|
+
// return fixer.remove(node);
|
|
33
|
+
// },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
export {
|
|
40
|
+
noImportExternalLibrary
|
|
41
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { fileIsServerFile, getFilename } from "../util";
|
|
3
|
+
const allowedPropKeySet = /* @__PURE__ */ new Set(["loader", "render", "of"]);
|
|
4
|
+
const nonScalarPropsRestricted = ESLintUtils.RuleCreator(() => __filename)({
|
|
5
|
+
name: "nonScalarPropsRestricted",
|
|
6
|
+
meta: {
|
|
7
|
+
type: "problem",
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Enforce that non-scalar props are not used."
|
|
10
|
+
},
|
|
11
|
+
messages: {
|
|
12
|
+
nonScalarPropsRestricted: "Error: Non-scalar props should not be used."
|
|
13
|
+
},
|
|
14
|
+
// fixable: "code",
|
|
15
|
+
schema: []
|
|
16
|
+
},
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
create(context) {
|
|
19
|
+
const filename = getFilename(context.filename);
|
|
20
|
+
const isServerFile = fileIsServerFile(context.filename, context.sourceCode.text);
|
|
21
|
+
if (!isServerFile || !["page.tsx", "layout.tsx"].includes(filename))
|
|
22
|
+
return {};
|
|
23
|
+
return {
|
|
24
|
+
JSXAttribute(node) {
|
|
25
|
+
if (node.value?.expression) {
|
|
26
|
+
const { expression, parent } = node.value;
|
|
27
|
+
if (expression.type === "ArrowFunctionExpression" || expression.type === "FunctionExpression") {
|
|
28
|
+
if (!allowedPropKeySet.has(parent.name.name))
|
|
29
|
+
context.report({
|
|
30
|
+
node,
|
|
31
|
+
messageId: "nonScalarPropsRestricted"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
export {
|
|
40
|
+
nonScalarPropsRestricted
|
|
41
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { fileHasContent, fileHasUseClient, fileIsServerFile } from "../util";
|
|
3
|
+
const useClientByFile = ESLintUtils.RuleCreator(() => __filename)({
|
|
4
|
+
name: "useClientByFile",
|
|
5
|
+
meta: {
|
|
6
|
+
type: "problem",
|
|
7
|
+
docs: {
|
|
8
|
+
description: "Enforce that `use client` is not used in `server` files."
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
noUseClient: 'Error: "use client" should not be used at the top of a `server` file.',
|
|
12
|
+
forceUseClient: 'Error: "use client" should be used at the top of a `client` file.'
|
|
13
|
+
},
|
|
14
|
+
// fixable: "code",
|
|
15
|
+
schema: []
|
|
16
|
+
},
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
create(context) {
|
|
19
|
+
const isServerFile = fileIsServerFile(context.filename, context.sourceCode.text);
|
|
20
|
+
if (isServerFile === null)
|
|
21
|
+
return {};
|
|
22
|
+
if (isServerFile)
|
|
23
|
+
return {
|
|
24
|
+
Program(node) {
|
|
25
|
+
if (!fileHasContent(context, node))
|
|
26
|
+
return;
|
|
27
|
+
const { hasUseClient, firstLineIdx } = fileHasUseClient(context);
|
|
28
|
+
if (hasUseClient)
|
|
29
|
+
context.report({
|
|
30
|
+
node: node.body[firstLineIdx],
|
|
31
|
+
messageId: "noUseClient"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
else
|
|
36
|
+
return {
|
|
37
|
+
Program(node) {
|
|
38
|
+
if (!fileHasContent(context, node))
|
|
39
|
+
return;
|
|
40
|
+
const { hasUseClient, firstLineIdx } = fileHasUseClient(context);
|
|
41
|
+
if (!hasUseClient)
|
|
42
|
+
context.report({
|
|
43
|
+
node: node.body[firstLineIdx],
|
|
44
|
+
messageId: "forceUseClient"
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
export {
|
|
51
|
+
useClientByFile
|
|
52
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const reactClientFunctionSet = /* @__PURE__ */ new Set([
|
|
2
|
+
"useState",
|
|
3
|
+
"useEffect",
|
|
4
|
+
"useContext",
|
|
5
|
+
"useReducer",
|
|
6
|
+
"useCallback",
|
|
7
|
+
"useMemo",
|
|
8
|
+
"useRef",
|
|
9
|
+
"useImperativeHandle",
|
|
10
|
+
"useLayoutEffect",
|
|
11
|
+
"useDebugValue"
|
|
12
|
+
]);
|
|
13
|
+
const frameworkClientFunctionSet = /* @__PURE__ */ new Set(["st"]);
|
|
14
|
+
const fileHasClientFunction = (node) => {
|
|
15
|
+
if (node.source.value === "react")
|
|
16
|
+
return node.specifiers.some((specifier) => reactClientFunctionSet.has(specifier.local.name));
|
|
17
|
+
else if (node.specifiers.some((specifier) => frameworkClientFunctionSet.has(specifier.local.name)))
|
|
18
|
+
return true;
|
|
19
|
+
else
|
|
20
|
+
return false;
|
|
21
|
+
};
|
|
22
|
+
export {
|
|
23
|
+
fileHasClientFunction
|
|
24
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const fileHasUseClient = (context) => {
|
|
2
|
+
const firstLineIdx = context.sourceCode.lines.findIndex((line) => line.trim() !== "");
|
|
3
|
+
return { hasUseClient: context.sourceCode.lines[firstLineIdx].includes('"use client"'), firstLineIdx };
|
|
4
|
+
};
|
|
5
|
+
export {
|
|
6
|
+
fileHasUseClient
|
|
7
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fileSuffixSet = /* @__PURE__ */ new Set([
|
|
2
|
+
"page.tsx",
|
|
3
|
+
"layout.tsx",
|
|
4
|
+
"index.ts",
|
|
5
|
+
"cnst_.ts",
|
|
6
|
+
"cnst.ts",
|
|
7
|
+
"db.ts",
|
|
8
|
+
"dict.ts",
|
|
9
|
+
"fetch.ts",
|
|
10
|
+
"option.ts",
|
|
11
|
+
"sig.ts",
|
|
12
|
+
"srv.ts",
|
|
13
|
+
"st.ts",
|
|
14
|
+
"usePage.ts",
|
|
15
|
+
"_server.ts",
|
|
16
|
+
"constant.ts",
|
|
17
|
+
"dictionary.ts",
|
|
18
|
+
"document.ts",
|
|
19
|
+
"service.ts",
|
|
20
|
+
"signal.ts",
|
|
21
|
+
"spec.ts",
|
|
22
|
+
"test.ts",
|
|
23
|
+
"store.ts",
|
|
24
|
+
"Template.tsx",
|
|
25
|
+
"Unit.tsx",
|
|
26
|
+
"Util.tsx",
|
|
27
|
+
"View.tsx",
|
|
28
|
+
"Zone.tsx",
|
|
29
|
+
"index.tsx"
|
|
30
|
+
]);
|
|
31
|
+
const fileIsPureImportFile = (filename) => {
|
|
32
|
+
const suffixFilename = filename.split("/").at(-1)?.split(".").slice(-2).join(".") ?? "";
|
|
33
|
+
if (fileSuffixSet.has(suffixFilename))
|
|
34
|
+
return true;
|
|
35
|
+
else
|
|
36
|
+
return false;
|
|
37
|
+
};
|
|
38
|
+
export {
|
|
39
|
+
fileIsPureImportFile
|
|
40
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const serverFileNameSet = /* @__PURE__ */ new Set();
|
|
2
|
+
const clientFileNameSet = /* @__PURE__ */ new Set(["st.ts", "store.ts"]);
|
|
3
|
+
const serverFileSuffixSet = /* @__PURE__ */ new Set(["page.tsx", "layout.tsx", "Unit.tsx", "View.tsx"]);
|
|
4
|
+
const clientFileSuffixSet = /* @__PURE__ */ new Set(["Zone.tsx", "Util.tsx", "Template.tsx"]);
|
|
5
|
+
const fileIsServerFile = (absFilePath, sourceCode) => {
|
|
6
|
+
if (absFilePath.includes("eslint-rules"))
|
|
7
|
+
return true;
|
|
8
|
+
const filePaths = absFilePath.split("/");
|
|
9
|
+
const filename = filePaths.at(-1) ?? "";
|
|
10
|
+
const fileLastName = absFilePath.split(".").slice(-2).join(".");
|
|
11
|
+
if (filename === "page.tsx") {
|
|
12
|
+
if (filePaths.includes("admin"))
|
|
13
|
+
return false;
|
|
14
|
+
else
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (filename === "layout.tsx") {
|
|
18
|
+
if (filePaths.at(-2) === "app")
|
|
19
|
+
return false;
|
|
20
|
+
else
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (serverFileNameSet.has(filename))
|
|
24
|
+
return true;
|
|
25
|
+
if (clientFileNameSet.has(filename))
|
|
26
|
+
return false;
|
|
27
|
+
if (serverFileSuffixSet.has(fileLastName))
|
|
28
|
+
return true;
|
|
29
|
+
if (clientFileSuffixSet.has(fileLastName))
|
|
30
|
+
return false;
|
|
31
|
+
if (new RegExp(/useEffect|useState|useContext|st\.do\.|st\.use\./g).test(sourceCode))
|
|
32
|
+
return false;
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
export {
|
|
36
|
+
fileIsServerFile
|
|
37
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { fileIsServerFile } from "./fileIsServerFile";
|
|
2
|
+
import { fileHasUseClient } from "./fileHasUseClient";
|
|
3
|
+
import { fileHasContent } from "./fileHasContent";
|
|
4
|
+
import { fileHasClientFunction } from "./fileHasClientFunction";
|
|
5
|
+
import { getFilename } from "./getFilename";
|
|
6
|
+
import { fileIsPureImportFile } from "./fileIsPureImportFile";
|
|
7
|
+
import { isInternalImport } from "./isInternalImport";
|
|
8
|
+
import { getAppName } from "./getAppName";
|
|
9
|
+
export {
|
|
10
|
+
fileHasClientFunction,
|
|
11
|
+
fileHasContent,
|
|
12
|
+
fileHasUseClient,
|
|
13
|
+
fileIsPureImportFile,
|
|
14
|
+
fileIsServerFile,
|
|
15
|
+
getAppName,
|
|
16
|
+
getFilename,
|
|
17
|
+
isInternalImport
|
|
18
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
const projectRoot = process.cwd();
|
|
3
|
+
const libNames = [...fs.readdirSync(`${projectRoot}/libs`), ...fs.readdirSync(`${projectRoot}/pkgs`)];
|
|
4
|
+
const internalImportSet = /* @__PURE__ */ new Set([
|
|
5
|
+
...libNames.map((libName) => `@${libName}`),
|
|
6
|
+
"react-icons",
|
|
7
|
+
"react",
|
|
8
|
+
"next",
|
|
9
|
+
"@radix-ui",
|
|
10
|
+
"@playwright",
|
|
11
|
+
"@akanjs",
|
|
12
|
+
".",
|
|
13
|
+
".."
|
|
14
|
+
]);
|
|
15
|
+
const isInternalImport = (importPaths, appName) => {
|
|
16
|
+
if (internalImportSet.has(importPaths[0]))
|
|
17
|
+
return true;
|
|
18
|
+
else if (importPaths[0] === appName)
|
|
19
|
+
return true;
|
|
20
|
+
else
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
export {
|
|
24
|
+
isInternalImport
|
|
25
|
+
};
|