@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 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
+ };
@@ -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.53",
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 fileHasContent = (context, node) => {
2
+ const firstToken = context.sourceCode.getFirstToken(node);
3
+ return !!firstToken;
4
+ };
5
+ export {
6
+ fileHasContent
7
+ };
@@ -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,8 @@
1
+ const getAppName = (filePaths) => {
2
+ const appsIdx = filePaths.findIndex((part) => part === "apps");
3
+ const appName = appsIdx === -1 ? null : filePaths[appsIdx + 1];
4
+ return appName;
5
+ };
6
+ export {
7
+ getAppName
8
+ };
@@ -0,0 +1,7 @@
1
+ const getFilename = (absFilePath) => {
2
+ const filePaths = absFilePath.split("/");
3
+ return filePaths.at(-1) ?? "";
4
+ };
5
+ export {
6
+ getFilename
7
+ };
@@ -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
+ };