@ovh-ux/manager-static-analysis-kit 0.2.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +108 -0
  3. package/bin/lint-cli.js +73 -0
  4. package/eslint.config.mjs +53 -0
  5. package/package.json +127 -0
  6. package/scripts/build.ts +44 -0
  7. package/src/adapters/code-health-meter/README.md +1 -0
  8. package/src/adapters/code-health-meter/config/.gitkeep +0 -0
  9. package/src/adapters/code-health-meter/rules/.gitkeep +0 -0
  10. package/src/adapters/eslint/README.md +317 -0
  11. package/src/adapters/eslint/config/eslint-shared-config.ts +60 -0
  12. package/src/adapters/eslint/rules/eslint-a11y.ts +30 -0
  13. package/src/adapters/eslint/rules/eslint-complexity.ts +87 -0
  14. package/src/adapters/eslint/rules/eslint-css.ts +35 -0
  15. package/src/adapters/eslint/rules/eslint-html.ts +32 -0
  16. package/src/adapters/eslint/rules/eslint-imports.ts +84 -0
  17. package/src/adapters/eslint/rules/eslint-javascript.ts +27 -0
  18. package/src/adapters/eslint/rules/eslint-naming-conventions.ts +49 -0
  19. package/src/adapters/eslint/rules/eslint-prettier.ts +29 -0
  20. package/src/adapters/eslint/rules/eslint-react.ts +50 -0
  21. package/src/adapters/eslint/rules/eslint-storybook.ts +29 -0
  22. package/src/adapters/eslint/rules/eslint-tailwind-jsx.ts +21 -0
  23. package/src/adapters/eslint/rules/eslint-tanstack.ts +25 -0
  24. package/src/adapters/eslint/rules/eslint-tests.ts +35 -0
  25. package/src/adapters/eslint/rules/eslint-typescript.ts +33 -0
  26. package/src/adapters/eslint/types/eslint-js.d.ts +4 -0
  27. package/src/adapters/eslint/types/eslint-plugin-jsx-a11y.d.ts +16 -0
  28. package/src/adapters/eslint/types/eslint-plugin-react-hooks.d.ts +9 -0
  29. package/src/adapters/eslint/types/eslint-plugin-react.d.ts +9 -0
  30. package/src/adapters/eslint/types/eslint-plugin-tailwindcss.d.ts +6 -0
  31. package/src/adapters/eslint/types/eslint-shared-config.d.ts +3 -0
  32. package/src/adapters/jscpd/README.md +0 -0
  33. package/src/adapters/jscpd/config/.gitkeep +0 -0
  34. package/src/adapters/jscpd/rules/.gitkeep +0 -0
  35. package/src/adapters/madge/README.md +0 -0
  36. package/src/adapters/madge/config/.gitkeep +0 -0
  37. package/src/adapters/madge/rules/.gitkeep +0 -0
  38. package/src/adapters/ts-config/README.md +153 -0
  39. package/src/adapters/ts-config/config/standard/tsconfig.node.ts +13 -0
  40. package/src/adapters/ts-config/config/standard/tsconfig.react.ts +15 -0
  41. package/src/adapters/ts-config/config/standard/tsconfig.test.ts +13 -0
  42. package/src/adapters/ts-config/config/strict/tsconfig.node.ts +13 -0
  43. package/src/adapters/ts-config/config/strict/tsconfig.react.ts +15 -0
  44. package/src/adapters/ts-config/config/strict/tsconfig.test.ts +13 -0
  45. package/src/adapters/ts-config/rules/tsconfig.base.strict.ts +13 -0
  46. package/src/adapters/ts-config/rules/tsconfig.base.ts +17 -0
  47. package/src/code-analysis.ts +1 -0
  48. package/src/configs/complexity-config.ts +29 -0
  49. package/src/configs/file-globs-config.ts +22 -0
  50. package/src/configs/import-order-config.ts +12 -0
  51. package/src/configs/jscpd-config.ts +8 -0
  52. package/src/configs/madge-config.ts +8 -0
  53. package/src/configs/naming-conventions-config.ts +138 -0
  54. package/src/configs/prettier-config.ts +81 -0
  55. package/src/configs/shared-exclusion-patterns.ts +41 -0
  56. package/tsconfig.json +31 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ # [0.2.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.1.0...@ovh-ux/manager-static-analysis-kit@0.2.0) (2025-07-17)
7
+
8
+
9
+ ### Features
10
+
11
+ * make static analysis kit public ([a998745](https://github.com/ovh/manager/commit/a998745cfb3101df0cab7a4530bca7cd87fc7025)), closes [#MANAGER-17954](https://github.com/ovh/manager/issues/MANAGER-17954)
12
+
13
+
14
+
15
+
16
+
17
+ # [0.1.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.0.1...@ovh-ux/manager-static-analysis-kit@0.1.0) (2025-07-17)
18
+
19
+
20
+ ### Features
21
+
22
+ * static analysis kit v1 - socle, architecture, eslint, tsconfig and deps ([5771071](https://github.com/ovh/manager/commit/577107168272904bf8a7a53a14a40b9ae52916ab)), closes [#MANAGER-17954](https://github.com/ovh/manager/issues/MANAGER-17954)
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # 🧪static-analysis-kit
2
+
3
+ A modular, extensible, and future-ready toolkit for defining, adapting, and sharing static analysis rules across projects in the `manager` monorepo.
4
+
5
+ ---
6
+
7
+ ## 🚀 What Is It?
8
+
9
+ `static-analysis-kit` provides a unified platform for code quality, complexity, duplication, and architectural analysis. It separates semantic rule definitions from tool-specific integrations using a clean adapter-based architecture.
10
+
11
+ It currently supports:
12
+ - ESLint (flat config)
13
+ - Madge (dependency graph & circular analysis)
14
+ - JSCPD (code duplication detection)
15
+ - Code Health Meter (complexity, Halstead, maintainability, SLOC, architecture)
16
+
17
+ ---
18
+
19
+ ## 🎯 Why We Built This
20
+
21
+ - Reuse rules across projects and tools
22
+ - Avoid duplication of `.eslintrc`, `.prettierrc`, `.madgerc`, etc.
23
+ - Standardize how we measure code health and enforce architectural boundaries
24
+ - Enable safe and gradual migration to new tooling
25
+
26
+ ---
27
+
28
+ ## 🧱 Structure Overview
29
+
30
+ ```
31
+ static-analysis-kit/
32
+ ├── src/
33
+ │ ├── code-analysis.ts # Central entry point (exports adapters)
34
+ │ ├── configs/ # Tool-agnostic semantic rule definitions
35
+ │ │ ├── complexity.ts
36
+ │ │ ├── madge-options.ts
37
+ │ │ └── jscpd-options.ts
38
+ │ └── adapters/
39
+ │ ├── eslint/ # ESLint flat config + rules
40
+ │ │ ├── config/
41
+ │ │ │ └── eslint.config.ts
42
+ │ │ └── rules/
43
+ │ ├── madge/ # Madge analysis adapter
44
+ │ ├── jscpd/ # JSCPD (duplication) adapter
45
+ │ ├── prettier/ # Optional: shared formatting config
46
+ │ └── code-health-meter/ # Halstead, MI, Cyclomatic, SLOC
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 🛠️ Usage in an App
52
+
53
+ ```ts
54
+ // apps/zimbra/eslint.config.ts
55
+ import { eslintConfig } from '@ovh-ux/manager-static-analysis-kit';
56
+
57
+ export default [...eslintConfig];
58
+ ```
59
+
60
+ This will apply all enabled ESLint rule groups from the toolkit, using flat config format (ESLint v8+).
61
+
62
+ ```ts
63
+ // apps/zimbra/prettier.config.ts
64
+ import { prettierConfig } from '@ovh-ux/manager-static-analysis-kit';
65
+
66
+ export default [...prettierConfig];
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 🧠 Design Principles
72
+
73
+ - **Composable**: Adapters are modular, rules are grouped by category
74
+ - **Decoupled**: Semantic configs live separately from tool logic
75
+ - **Pluggable**: Easy to add new tools (e.g. Biome, Oxlint)
76
+ - **Testable**: Configs are plain TS, easy to mock and verify
77
+
78
+ ---
79
+
80
+ ## 🔮 Future Flexibility
81
+
82
+ This architecture is designed to easily accommodate:
83
+
84
+ - `biome/` adapter (Rust-based formatter/linter)
85
+ - `oxlint/` adapter (high-performance Rust linter)
86
+ - `depcruise/`, `pmd/`, `stylelint/` or internal tools
87
+
88
+ Each would follow the same adapter pattern and pull from shared `configs/` where applicable.
89
+
90
+ ---
91
+
92
+ ## 🧑‍💻 Contributing
93
+
94
+ If you’re adding a new rule or adapter:
95
+
96
+ 1. Put semantic/shared config in `configs/` if reusable.
97
+ 2. Create a new adapter directory in `adapters/` (e.g., `eslint`, `madge`).
98
+ 3. Add a domain-specific config, transformer, or wrapper.
99
+ 4. Wire into `code-analysis.ts` as needed.
100
+ 5. Document your addition.
101
+
102
+ ---
103
+
104
+ ## 📬 Feedback Welcome
105
+
106
+ The goal is to create a robust, composable, and organization-wide standard for static analysis.
107
+
108
+ Feel free to suggest improvements or new integrations!
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'child_process';
3
+ import { fileURLToPath, pathToFileURL } from 'url';
4
+ import { dirname, resolve, join } from 'path';
5
+ import { cwd } from 'process';
6
+ import { existsSync } from 'fs';
7
+
8
+ // eslint-disable-next-line no-underscore-dangle
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const userCwd = cwd();
11
+ const eslintBin = resolve(__dirname, '../node_modules/.bin/eslint');
12
+
13
+ const args = process.argv.slice(2);
14
+ const hasExplicitConfigArg = args.includes('--config');
15
+ const localConfigPath = join(userCwd, 'eslint.config.ts');
16
+ const sharedDefaultPath = resolve(__dirname, '../dist/adapters/eslint/config/eslint.config.js');
17
+ const hasLocalConfig = existsSync(localConfigPath);
18
+
19
+ // Determine effective config path
20
+ // eslint-disable-next-line no-nested-ternary
21
+ const configPath = hasExplicitConfigArg
22
+ ? args[args.indexOf('--config') + 1]
23
+ : hasLocalConfig
24
+ ? localConfigPath
25
+ : sharedDefaultPath;
26
+
27
+ // Helper: wrap spawn in a promise
28
+ function runLint(command, args, cwd) {
29
+ return new Promise((resolve, reject) => {
30
+ const child = spawn(command, args, {
31
+ stdio: 'inherit',
32
+ cwd,
33
+ shell: true,
34
+ });
35
+
36
+ child.on('error', (err) => {
37
+ console.error('[manager-lint] Failed to run ESLint');
38
+ console.error(err);
39
+ reject(1);
40
+ });
41
+
42
+ child.on('close', (code) => {
43
+ resolve(code ?? 1);
44
+ });
45
+ });
46
+ }
47
+
48
+ (async () => {
49
+ // Handle --print-config
50
+ if (args.includes('--print-config')) {
51
+ const url = pathToFileURL(configPath).href;
52
+ try {
53
+ const mod = await import(url);
54
+ const configContent = mod.default ?? mod;
55
+ console.log('[manager-lint] Loaded ESLint config from:', configPath);
56
+ console.dir(configContent, { depth: null });
57
+ } catch (err) {
58
+ console.error(`[manager-lint] Failed to load config at ${configPath}`);
59
+ console.error(err);
60
+ process.exit(1);
61
+ }
62
+ process.exit(0);
63
+ }
64
+
65
+ // Construct full args
66
+ const fullArgs = hasExplicitConfigArg || hasLocalConfig
67
+ ? args
68
+ : ['--config', sharedDefaultPath, ...args];
69
+
70
+ // Run ESLint using tsx and wait for completion
71
+ const exitCode = await runLint('tsx', [eslintBin, ...fullArgs], userCwd);
72
+ process.exit(exitCode);
73
+ })();
@@ -0,0 +1,53 @@
1
+ import { javascriptEslintConfig } from './src/adapters/eslint/rules/eslint-javascript'
2
+ import { typescriptEslintConfig } from './src/adapters/eslint/rules/eslint-typescript';
3
+ import { reactEslintConfig } from './src/adapters/eslint/rules/eslint-react';
4
+ import { tanStackQueryEslintConfig } from './src/adapters/eslint/rules/eslint-tanstack';
5
+ import { importEslintConfig } from './src/adapters/eslint/rules/eslint-imports';
6
+ import { checkFileEslintConfig } from './src/adapters/eslint/rules/eslint-naming-conventions';
7
+ import { vitestEslintConfig } from './src/adapters/eslint/rules/eslint-tests';
8
+ import { prettierEslintConfig } from './src/adapters/eslint/rules/eslint-prettier';
9
+ import { complexityJsxTsxConfig, complexityTsJsConfig } from './src/adapters/eslint/rules/eslint-complexity';
10
+
11
+ export default [
12
+ javascriptEslintConfig,
13
+ typescriptEslintConfig,
14
+ reactEslintConfig,
15
+ tanStackQueryEslintConfig,
16
+ ...importEslintConfig,
17
+ ...checkFileEslintConfig,
18
+ vitestEslintConfig,
19
+ prettierEslintConfig,
20
+ complexityJsxTsxConfig,
21
+ complexityTsJsConfig,
22
+ {
23
+ ignores: [
24
+ '**/*.md',
25
+ '**/coverage/**',
26
+ '**/dist/**',
27
+ '**/*.d.ts',
28
+ ],
29
+ },
30
+ {
31
+ rules: {
32
+ '@typescript-eslint/no-explicit-any': 'off',
33
+ '@typescript-eslint/no-unsafe-assignment': 'off',
34
+ '@typescript-eslint/no-unsafe-member-access': 'off',
35
+ },
36
+ },
37
+ {
38
+ files: ['**/prettier-config.ts', '**/*.config.{ts,js,mjs}'],
39
+ rules: {
40
+ 'import/no-nodejs-modules': 'off',
41
+ },
42
+ },
43
+ {
44
+ files: [
45
+ '**/ts-config/config/standard/tsconfig.test.ts',
46
+ '**/ts-config/config/strict/tsconfig.test.ts',
47
+ ],
48
+ rules: {
49
+ 'check-file/folder-match-with-fex': 'off',
50
+ 'check-file/filename-naming-convention': 'off',
51
+ },
52
+ }
53
+ ];
package/package.json ADDED
@@ -0,0 +1,127 @@
1
+ {
2
+ "name": "@ovh-ux/manager-static-analysis-kit",
3
+ "version": "0.2.0",
4
+ "description": "A modular, extensible, and future-ready toolkit for defining, adapting, and sharing static analysis rules across projects in the `manager` monorepo.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/ovh/manager.git",
8
+ "directory": "packages/manager/tools/static-analysis-kit"
9
+ },
10
+ "license": "BSD-3-Clause",
11
+ "author": "OVH SAS",
12
+ "type": "module",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/code-analysis.js"
16
+ },
17
+ "./eslint/config": {
18
+ "import": "./dist/adapters/eslint/config/eslint-shared-config.js"
19
+ },
20
+ "./eslint/storybook": {
21
+ "import": "./dist/adapters/eslint/rules/eslint-storybook.js"
22
+ },
23
+ "./eslint/a11y": {
24
+ "import": "./dist/adapters/eslint/rules/eslint-a11y.js"
25
+ },
26
+ "./eslint/complexity": {
27
+ "import": "./dist/adapters/eslint/rules/eslint-complexity.js"
28
+ },
29
+ "./eslint/css": {
30
+ "import": "./dist/adapters/eslint/rules/eslint-css.js"
31
+ },
32
+ "./eslint/html": {
33
+ "import": "./dist/adapters/eslint/rules/eslint-html.js"
34
+ },
35
+ "./eslint/imports": {
36
+ "import": "./dist/adapters/eslint/rules/eslint-imports.js"
37
+ },
38
+ "./eslint/javascript": {
39
+ "import": "./dist/adapters/eslint/rules/eslint-javascript.js"
40
+ },
41
+ "./eslint/naming-conventions": {
42
+ "import": "./dist/adapters/eslint/rules/eslint-naming-conventions.js"
43
+ },
44
+ "./eslint/prettier": {
45
+ "import": "./dist/adapters/eslint/rules/eslint-prettier.js"
46
+ },
47
+ "./eslint/react": {
48
+ "import": "./dist/adapters/eslint/rules/eslint-react.js"
49
+ },
50
+ "./eslint/tailwind-jsx": {
51
+ "import": "./dist/adapters/eslint/rules/eslint-tailwind-jsx.js"
52
+ },
53
+ "./eslint/tanstack": {
54
+ "import": "./dist/adapters/eslint/rules/eslint-tanstack.js"
55
+ },
56
+ "./eslint/tests": {
57
+ "import": "./dist/adapters/eslint/rules/eslint-tests.js"
58
+ },
59
+ "./eslint/typescript": {
60
+ "import": "./dist/adapters/eslint/rules/eslint-typescript.js"
61
+ },
62
+ "./tsconfig/react": {
63
+ "default": "./dist/tsconfig/react.json"
64
+ },
65
+ "./tsconfig/node": {
66
+ "default": "./dist/tsconfig/node.json"
67
+ },
68
+ "./tsconfig/test": {
69
+ "default": "./dist/tsconfig/test.json"
70
+ },
71
+ "./tsconfig/react-strict": {
72
+ "default": "./dist/tsconfig/react-strict.json"
73
+ },
74
+ "./tsconfig/node-strict": {
75
+ "default": "./dist/tsconfig/node-strict.json"
76
+ },
77
+ "./tsconfig/test-strict": {
78
+ "default": "./dist/tsconfig/test-strict.json"
79
+ }
80
+ },
81
+ "main": "./dist/code-analysis.js",
82
+ "types": "./dist/code-analysis.d.ts",
83
+ "bin": {
84
+ "manager-lint": "./bin/lint-cli.js"
85
+ },
86
+ "scripts": {
87
+ "build": "tsx scripts/build.ts",
88
+ "lint:modern": "manager-lint --config eslint.config.mjs ./src",
89
+ "lint:modern:fix": "manager-lint --fix --config eslint.config.mjs ./src",
90
+ "test": "echo \"No tests yet.\" && exit 0"
91
+ },
92
+ "devDependencies": {
93
+ "@eslint/css": "^0.9.0",
94
+ "@eslint/js": "^9.29.0",
95
+ "@html-eslint/eslint-plugin": "^0.42.0",
96
+ "@html-eslint/parser": "^0.42.0",
97
+ "@tanstack/eslint-plugin-query": "^5.81.2",
98
+ "@trivago/prettier-plugin-sort-imports": "^5.2.2",
99
+ "@types/eslint": "^8.56.12",
100
+ "@types/eslint-plugin-tailwindcss": "^3.17.0",
101
+ "@types/node": "^24.0.4",
102
+ "@typescript-eslint/eslint-plugin": "^7.18.1-alpha.3",
103
+ "@typescript-eslint/parser": "^7.18.1-alpha.3",
104
+ "@vitest/eslint-plugin": "^1.3.3",
105
+ "eslint": "^9.29.0",
106
+ "eslint-config-prettier": "^10.1.5",
107
+ "eslint-import-resolver-typescript": "^4.4.4",
108
+ "eslint-plugin-check-file": "^3.3.0",
109
+ "eslint-plugin-import": "^2.32.0",
110
+ "eslint-plugin-jsx-a11y": "^6.10.2",
111
+ "eslint-plugin-prettier": "^5.0.0",
112
+ "eslint-plugin-react": "^7.37.5",
113
+ "eslint-plugin-react-hooks": "^5.2.0",
114
+ "eslint-plugin-storybook": "^9.0.13",
115
+ "eslint-plugin-tailwindcss": "^3.18.0",
116
+ "globals": "^16.2.0",
117
+ "prettier": "^3.6.2",
118
+ "prettier-plugin-tailwindcss": "^0.6.13",
119
+ "tailwind-csstree": "^0.1.1",
120
+ "tsx": "^4.20.3",
121
+ "typescript": "^5.8.3",
122
+ "typescript-eslint": "^8.35.0"
123
+ },
124
+ "peerDependencies": {
125
+ "eslint": "^9.29.0"
126
+ }
127
+ }
@@ -0,0 +1,44 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { writeFileSync, rmSync, mkdirSync, existsSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ (async () => {
7
+ // @ts-ignore
8
+ const root = resolve(import.meta.dirname, '..');
9
+ const dist = resolve(root, 'dist');
10
+
11
+ console.log('🧹 Cleaning dist...');
12
+ rmSync(dist, { recursive: true, force: true });
13
+
14
+ console.log('🛠️ Building TypeScript...');
15
+ execSync('tsc --project tsconfig.json', { stdio: 'inherit' });
16
+
17
+ console.log('📦 Flattening TypeScript config files...');
18
+
19
+ const configs: [string, string][] = [
20
+ ['src/adapters/ts-config/config/standard/tsconfig.react.ts', 'tsconfig/react.json'],
21
+ ['src/adapters/ts-config/config/standard/tsconfig.node.ts', 'tsconfig/node.json'],
22
+ ['src/adapters/ts-config/config/standard/tsconfig.test.ts', 'tsconfig/test.json'],
23
+ ['src/adapters/ts-config/config/strict/tsconfig.react.ts', 'tsconfig/react-strict.json'],
24
+ ['src/adapters/ts-config/config/strict/tsconfig.node.ts', 'tsconfig/node-strict.json'],
25
+ ['src/adapters/ts-config/config/strict/tsconfig.test.ts', 'tsconfig/test-strict.json'],
26
+ ];
27
+
28
+ for (const [sourcePath, destPath] of configs) {
29
+ const srcFullPath = resolve(root, sourcePath);
30
+ const destFullPath = resolve(dist, destPath);
31
+ const destDir = resolve(dist, destPath.split('/').slice(0, -1).join('/'));
32
+
33
+ if (!existsSync(destDir)) {
34
+ mkdirSync(destDir, { recursive: true });
35
+ }
36
+
37
+ const configModule = await import(pathToFileURL(srcFullPath).href);
38
+ const config = configModule.default;
39
+
40
+ writeFileSync(destFullPath, JSON.stringify(config, null, 2), 'utf8');
41
+ }
42
+
43
+ console.log('✅ Build completed with flattened tsconfigs.');
44
+ })();
@@ -0,0 +1 @@
1
+ # code-health-meter
File without changes
File without changes