entkapp 5.1.0 → 5.2.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "name": "entkapp",
4
- "version": "5.1.0",
4
+ "version": "5.2.1",
5
5
  "description": "The Ultimate Enterprise Codebase Janitor. Faster than Knip with OXC integration, type-aware analysis, and automated structural healing. Fully standalone - solving what Knip cannot.",
6
6
  "type": "module",
7
7
  "main": "index.js",
@@ -86,10 +86,16 @@ export class GraphNode {
86
86
  }
87
87
 
88
88
  // Fuzzy / Dynamic usage (Identifiers, Strings, JSX, Decorators)
89
- if (parentNode.instantiatedIdentifiers.has(symbolName)) return true;
90
- if (parentNode.rawStringReferences.has(symbolName)) return true;
91
- if (parentNode.jsxComponents.has(symbolName)) return true;
92
- if (parentNode.decorators.has(symbolName)) return true;
89
+ // UPGRADE: Only check for fuzzy matches if we have a reason to believe it might be used.
90
+ // We skip global names like 'toLowerCase' unless they are explicitly imported.
91
+ const isGlobalName = ['toLowerCase', 'toUpperCase', 'toString', 'valueOf', 'hasOwnProperty', 'constructor'].includes(symbolName);
92
+
93
+ if (!isGlobalName) {
94
+ if (parentNode.instantiatedIdentifiers.has(symbolName)) return true;
95
+ if (parentNode.rawStringReferences.has(symbolName)) return true;
96
+ if (parentNode.jsxComponents.has(symbolName)) return true;
97
+ if (parentNode.decorators.has(symbolName)) return true;
98
+ }
93
99
 
94
100
  for (const accessChain of parentNode.propertyAccessChains) {
95
101
  if (accessChain.endsWith(`.${symbolName}`) || accessChain.includes(`.${symbolName}.`)) return true;
@@ -189,7 +195,7 @@ export class EngineContext {
189
195
  });
190
196
 
191
197
  // --- PLUGIN-BASED ECOSYSTEM DETECTION ---
192
- // Plugins will now handle their own erreichbarkeit and dependency validation.
198
+ // Plugins will now handle their own reachability and dependency validation.
193
199
  if (this.pluginRegistry) {
194
200
  const activePlugins = await this.pluginRegistry.getActivePlugins(pkgDir);
195
201
  for (const plugin of activePlugins) {
@@ -390,7 +396,15 @@ export class EngineContext {
390
396
  continue;
391
397
  }
392
398
 
393
- if (!usedByReachableFiles.has(dep)) {
399
+ // UPGRADE: Be more aggressive in detecting unused dependencies.
400
+ // Check both externalPackageUsage and rawStringReferences.
401
+ const isUsed = usedByReachableFiles.has(dep) ||
402
+ Array.from(reachableFiles).some(f => {
403
+ const node = this.projectGraph.get(f);
404
+ return node && (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep));
405
+ });
406
+
407
+ if (!isUsed) {
394
408
  report.unusedDependencies.push({
395
409
  package: dep,
396
410
  type: deps.dependencies.includes(dep) ? 'dependency' : 'devDependency',
@@ -0,0 +1,82 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { glob } from 'glob';
4
+
5
+ /**
6
+ * Entkapp Initializer: Analyzes the codebase to generate a package.json
7
+ * with correctly categorized dependencies and devDependencies.
8
+ */
9
+ export class Initializer {
10
+ constructor(context) {
11
+ this.context = context;
12
+ }
13
+
14
+ async run() {
15
+ console.log("🚀 Starting entkapp intelligent initialization...");
16
+ const cwd = this.context.cwd || process.cwd();
17
+
18
+ // 1. Scan for all JS/TS files
19
+ const files = await glob('**/*.{js,ts,jsx,tsx,vue,svelte,astro}', {
20
+ ignore: ['node_modules/**', 'dist/**', '.git/**'],
21
+ cwd
22
+ });
23
+
24
+ const dependencies = new Set();
25
+ const devDependencies = new Set();
26
+
27
+ // Common dev tool indicators
28
+ const devIndicators = ['test', 'spec', 'config', 'stories', 'mock'];
29
+
30
+ console.log(`🔍 Analyzing ${files.length} files for imports...`);
31
+
32
+ for (const file of files) {
33
+ const content = await fs.readFile(path.join(cwd, file), 'utf8');
34
+ const isDevFile = devIndicators.some(ind => file.toLowerCase().includes(ind));
35
+
36
+ // Simple but effective regex for imports
37
+ const importMatches = content.matchAll(/(?:import|from|require)\s*\(?\s*['"]([^' "./][^'"]*)['"]/g);
38
+
39
+ for (const match of importMatches) {
40
+ const pkg = this.extractPackageName(match[1]);
41
+ if (pkg && !this.isBuiltIn(pkg)) {
42
+ if (isDevFile) devDependencies.add(pkg);
43
+ else dependencies.add(pkg);
44
+ }
45
+ }
46
+ }
47
+
48
+ // 2. Generate package.json
49
+ const pkgJson = {
50
+ name: path.basename(cwd),
51
+ version: "1.0.0",
52
+ description: "Initialized with entkapp",
53
+ main: "index.js",
54
+ scripts: {
55
+ "entkapp:run": "entkapp -r",
56
+ "entkapp:check": "entkapp --verbose"
57
+ },
58
+ dependencies: Object.fromEntries([...dependencies].sort().map(d => [d, "latest"])),
59
+ devDependencies: Object.fromEntries([...devDependencies].sort().map(d => [d, "latest"]))
60
+ };
61
+
62
+ await fs.writeFile(path.join(cwd, 'package.json'), JSON.stringify(pkgJson, null, 2));
63
+ console.log("✅ package.json generated with analyzed dependencies.");
64
+
65
+ // 3. Create /entkapp folder
66
+ await fs.mkdir(path.join(cwd, 'entkapp'), { recursive: true });
67
+ console.log("✅ /entkapp configuration folder created.");
68
+ }
69
+
70
+ extractPackageName(specifier) {
71
+ if (specifier.startsWith('@')) {
72
+ const parts = specifier.split('/');
73
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
74
+ }
75
+ return specifier.split('/')[0];
76
+ }
77
+
78
+ isBuiltIn(pkg) {
79
+ const builtins = ['path', 'fs', 'os', 'crypto', 'http', 'https', 'stream', 'util', 'events', 'module', 'process'];
80
+ return builtins.includes(pkg) || pkg.startsWith('node:');
81
+ }
82
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * CodeSmellAnalyzer: Performs deep static analysis to find runtime risks and logic smells.
3
+ */
4
+ export class CodeSmellAnalyzer {
5
+ constructor(context) {
6
+ this.context = context;
7
+ this.issues = [];
8
+ this.rules = {
9
+ 'potential-null-pointer': {
10
+ message: 'Potential Null Pointer Risk: Accessing property on an object that might be undefined.',
11
+ link: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property'
12
+ },
13
+ 'infinite-loop-risk': {
14
+ message: 'Potential Infinite Loop: Loop condition does not seem to change within the body.',
15
+ link: 'https://en.wikipedia.org/wiki/Infinite_loop'
16
+ },
17
+ 'implicit-type-coercion': {
18
+ message: 'Implicit Type Coercion: Using loose equality (==) can lead to unexpected runtime behavior.',
19
+ link: 'https://dorey.github.io/JavaScript-Equality-Table/'
20
+ }
21
+ };
22
+ }
23
+
24
+ analyze(fileNode) {
25
+ if (!fileNode.ast) return;
26
+ this.walk(fileNode.ast, fileNode);
27
+ }
28
+
29
+ walk(node, fileNode) {
30
+ if (!node) return;
31
+
32
+ // 1. Check for Loose Equality (==)
33
+ if (node.type === 'BinaryExpression' && (node.operator === '==' || node.operator === '!=')) {
34
+ this.addIssue('implicit-type-coercion', node, fileNode);
35
+ }
36
+
37
+ // 2. Check for Potential Infinite Loops (While loops with static conditions)
38
+ if (node.type === 'WhileStatement' && node.test.type === 'Literal' && node.test.value === true) {
39
+ // Check if there is a break or return inside
40
+ if (!this.hasExitStatement(node.body)) {
41
+ this.addIssue('infinite-loop-risk', node, fileNode);
42
+ }
43
+ }
44
+
45
+ // 3. Check for Null Pointer Risks (Accessing properties on potentially uninitialized vars)
46
+ if (node.type === 'MemberExpression' && node.object.type === 'Identifier') {
47
+ const varName = node.object.name;
48
+ if (this.isPotentiallyNull(varName, fileNode)) {
49
+ this.addIssue('potential-null-pointer', node, fileNode);
50
+ }
51
+ }
52
+
53
+ // Recursively walk the AST
54
+ for (const key in node) {
55
+ if (node[key] && typeof node[key] === 'object') {
56
+ if (Array.isArray(node[key])) {
57
+ node[key].forEach(child => this.walk(child, fileNode));
58
+ } else {
59
+ this.walk(node[key], fileNode);
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ addIssue(ruleId, node, fileNode) {
66
+ const rule = this.rules[ruleId];
67
+ fileNode.diagnostics = fileNode.diagnostics || [];
68
+ fileNode.diagnostics.push({
69
+ ruleId,
70
+ message: rule.message,
71
+ link: rule.link,
72
+ line: node.start ? this.getLineNumber(node.start, fileNode.content) : 0,
73
+ severity: 'warning'
74
+ });
75
+ }
76
+
77
+ hasExitStatement(node) {
78
+ let found = false;
79
+ const check = (n) => {
80
+ if (!n || found) return;
81
+ if (n.type === 'BreakStatement' || n.type === 'ReturnStatement' || n.type === 'ThrowStatement') {
82
+ found = true;
83
+ return;
84
+ }
85
+ for (const key in n) {
86
+ if (n[key] && typeof n[key] === 'object') {
87
+ if (Array.isArray(n[key])) n[key].forEach(check);
88
+ else check(n[key]);
89
+ }
90
+ }
91
+ };
92
+ check(node);
93
+ return found;
94
+ }
95
+
96
+ isPotentiallyNull(name, fileNode) {
97
+ // Simple heuristic: if it's a variable declared without init or in a try-catch
98
+ const symbol = fileNode.symbolTable?.get(name);
99
+ return symbol && symbol.type === 'variable' && !symbol.initialized;
100
+ }
101
+
102
+ getLineNumber(pos, content) {
103
+ if (!content) return 0;
104
+ return content.substring(0, pos).split('\n').length;
105
+ }
106
+ }