pumuki-ast-hooks 5.5.50 → 5.5.51

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.
@@ -0,0 +1,57 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+ const env = require(path.join(__dirname, '../../../../config/env'));
5
+
6
+ class SwiftToolchainResolver {
7
+ constructor(options = {}) {
8
+ this.sourceKittenPath = options.sourceKittenPath || '/opt/homebrew/bin/sourcekitten';
9
+ this.projectRoot = options.projectRoot || this.detectRepoRoot();
10
+ this.swiftSyntaxPath = this.findSwiftSyntaxBinary(this.projectRoot);
11
+ }
12
+
13
+ detectRepoRoot() {
14
+ try {
15
+ return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
16
+ } catch {
17
+ return process.cwd();
18
+ }
19
+ }
20
+
21
+ resolveAuditTmpDir(repoRoot) {
22
+ const configured = String(env.get('AUDIT_TMP', '') || '').trim();
23
+ if (configured.length > 0) {
24
+ return path.isAbsolute(configured) ? configured : path.join(repoRoot, configured);
25
+ }
26
+ return path.join(repoRoot, '.audit_tmp');
27
+ }
28
+
29
+ checkSourceKitten() {
30
+ try {
31
+ execSync(`${this.sourceKittenPath} version`, { stdio: 'pipe' });
32
+ return true;
33
+ } catch (error) {
34
+ if (process.env.DEBUG) {
35
+ console.debug(`[SwiftToolchainResolver] SourceKitten not available at ${this.sourceKittenPath}: ${error.message}`);
36
+ }
37
+ return false;
38
+ }
39
+ }
40
+
41
+ findSwiftSyntaxBinary(projectRoot) {
42
+ const possiblePaths = [
43
+ path.join(__dirname, '../../../../../CustomLintRules/.build/debug/swift-ast-analyzer'),
44
+ path.join(projectRoot, 'CustomLintRules/.build/debug/swift-ast-analyzer'),
45
+ path.join(projectRoot, '.build/debug/swift-ast-analyzer'),
46
+ ];
47
+
48
+ for (const p of possiblePaths) {
49
+ if (fs.existsSync(p)) {
50
+ return p;
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+ }
56
+
57
+ module.exports = { SwiftToolchainResolver };
@@ -1,7 +1,6 @@
1
1
  const { execSync } = require('child_process');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
- const crypto = require('crypto');
5
4
  const env = require(path.join(__dirname, '../../../../config/env'));
6
5
  const {
7
6
  resetCollections,
@@ -9,13 +8,21 @@ const {
9
8
  analyzeCollectedNodes,
10
9
  finalizeGodClassDetection,
11
10
  } = require('../detectors/ios-ast-intelligent-strategies');
11
+ const { SwiftToolchainResolver } = require('./SwiftToolchainResolver');
12
+ const { StagedSwiftFilePreparer } = require('./StagedSwiftFilePreparer');
13
+ const { SwiftAstRunner } = require('./SwiftAstRunner');
14
+ const { iOSAstAnalysisOrchestrator } = require('./iOSAstAnalysisOrchestrator');
12
15
 
13
16
  class iOSASTIntelligentAnalyzer {
14
17
  constructor(findings) {
15
18
  this.findings = findings;
16
- this.sourceKittenPath = '/opt/homebrew/bin/sourcekitten';
17
- this.isAvailable = this.checkSourceKitten();
18
- this.hasSwiftSyntax = this.checkSwiftSyntax();
19
+ this.toolchain = new SwiftToolchainResolver();
20
+ this.sourceKittenPath = this.toolchain.sourceKittenPath;
21
+ this.isAvailable = this.toolchain.checkSourceKitten();
22
+ this.swiftSyntaxPath = this.toolchain.swiftSyntaxPath;
23
+ this.hasSwiftSyntax = Boolean(this.swiftSyntaxPath);
24
+ this.swiftRunner = new SwiftAstRunner({ toolchain: this.toolchain });
25
+ this.analysisOrchestrator = new iOSAstAnalysisOrchestrator();
19
26
  this.fileContent = '';
20
27
  this.currentFilePath = '';
21
28
  this.godClassCandidates = [];
@@ -29,99 +36,6 @@ class iOSASTIntelligentAnalyzer {
29
36
  this.closures = [];
30
37
  }
31
38
 
32
- resolveAuditTmpDir(repoRoot) {
33
- const configured = String(env.get('AUDIT_TMP', '') || '').trim();
34
- if (configured.length > 0) {
35
- return path.isAbsolute(configured) ? configured : path.join(repoRoot, configured);
36
- }
37
- return path.join(repoRoot, '.audit_tmp');
38
- }
39
-
40
- safeTempFilePath(repoRoot, displayPath) {
41
- const tmpDir = this.resolveAuditTmpDir(repoRoot);
42
- const hash = crypto.createHash('sha1').update(String(displayPath)).digest('hex').slice(0, 10);
43
- const base = path.basename(displayPath, '.swift');
44
- const filename = `${base}.${hash}.staged.swift`;
45
- return path.join(tmpDir, filename);
46
- }
47
-
48
- readStagedFileContent(repoRoot, relPath) {
49
- try {
50
- return execSync(`git show :"${relPath}"`, { encoding: 'utf8', cwd: repoRoot, stdio: ['ignore', 'pipe', 'pipe'] });
51
- } catch (error) {
52
- if (process.env.DEBUG) {
53
- console.debug(`[iOSASTIntelligentAnalyzer] Failed to read staged file ${relPath}: ${error.message}`);
54
- }
55
- return null;
56
- }
57
- }
58
-
59
- checkSourceKitten() {
60
- try {
61
- execSync(`${this.sourceKittenPath} version`, { stdio: 'pipe' });
62
- return true;
63
- } catch (error) {
64
- if (process.env.DEBUG) {
65
- console.debug(`[iOSASTIntelligentAnalyzer] SourceKitten not available at ${this.sourceKittenPath}: ${error.message}`);
66
- }
67
- return false;
68
- }
69
- }
70
-
71
- checkSwiftSyntax() {
72
- const projectRoot = require('child_process')
73
- .execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' })
74
- .trim();
75
-
76
- const possiblePaths = [
77
- path.join(__dirname, '../../../../../CustomLintRules/.build/debug/swift-ast-analyzer'),
78
- path.join(projectRoot, 'CustomLintRules/.build/debug/swift-ast-analyzer'),
79
- path.join(projectRoot, '.build/debug/swift-ast-analyzer')
80
- ];
81
- for (const p of possiblePaths) {
82
- if (fs.existsSync(p)) {
83
- this.swiftSyntaxPath = p;
84
- return true;
85
- }
86
- }
87
- return false;
88
- }
89
-
90
- analyzeWithSwiftSyntax(filePath, displayPath = null) {
91
- if (!this.swiftSyntaxPath) return;
92
- try {
93
- const result = execSync(`"${this.swiftSyntaxPath}" "${filePath}"`, {
94
- encoding: 'utf8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe']
95
- });
96
- const violations = JSON.parse(result);
97
- for (const v of violations) {
98
- const reportedPath = displayPath || filePath;
99
- this.findings.push({
100
- ruleId: v.ruleId, severity: v.severity, filePath: reportedPath,
101
- line: v.line, column: v.column, message: v.message
102
- });
103
- }
104
- } catch (error) {
105
- console.error('[iOSASTIntelligentAnalyzer] Error parsing file:', error.message);
106
- }
107
- }
108
-
109
- parseFile(filePath) {
110
- if (!this.isAvailable) return null;
111
- try {
112
- const result = execSync(
113
- `${this.sourceKittenPath} structure --file "${filePath}"`,
114
- { encoding: 'utf8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] }
115
- );
116
- return JSON.parse(result);
117
- } catch (error) {
118
- if (process.env.DEBUG) {
119
- console.debug(`[iOSASTIntelligentAnalyzer] SourceKitten parse failed for ${filePath}: ${error.message}`);
120
- }
121
- return null;
122
- }
123
- }
124
-
125
39
  analyzeFile(filePath, options = {}) {
126
40
  if (!filePath || !String(filePath).endsWith('.swift')) return;
127
41
 
@@ -129,53 +43,29 @@ class iOSASTIntelligentAnalyzer {
129
43
  const displayPath = options.displayPath || filePath;
130
44
  const stagedRelPath = options.stagedRelPath || null;
131
45
  const stagingOnly = env.get('STAGING_ONLY_MODE', '0') === '1';
46
+ const auditTmpDir = this.toolchain.resolveAuditTmpDir(repoRoot);
47
+ const stagedPreparer = new StagedSwiftFilePreparer({ repoRoot, auditTmpDir });
132
48
 
133
- let parsePath = filePath;
134
- let contentOverride = null;
135
-
136
- if (stagingOnly && stagedRelPath) {
137
- const stagedContent = this.readStagedFileContent(repoRoot, stagedRelPath);
138
- if (typeof stagedContent === 'string') {
139
- const tmpPath = this.safeTempFilePath(repoRoot, stagedRelPath);
140
- try {
141
- fs.mkdirSync(path.dirname(tmpPath), { recursive: true });
142
- fs.writeFileSync(tmpPath, stagedContent, 'utf8');
143
- parsePath = tmpPath;
144
- contentOverride = stagedContent;
145
- } catch (error) {
146
- if (process.env.DEBUG) {
147
- console.debug(`[iOSASTIntelligentAnalyzer] Failed to write temp staged file ${tmpPath}: ${error.message}`);
148
- }
149
- // Fall back to working tree file
150
- }
151
- }
152
- }
49
+ const { parsePath, contentOverride } = stagedPreparer.prepare({
50
+ filePath,
51
+ stagedRelPath,
52
+ stagingOnly
53
+ });
153
54
 
154
55
  if (this.hasSwiftSyntax) {
155
- this.analyzeWithSwiftSyntax(parsePath, displayPath);
56
+ this.swiftRunner.runSwiftSyntax(parsePath, displayPath, this.findings);
156
57
  }
157
58
 
158
- const ast = this.parseFile(parsePath);
59
+ const ast = this.swiftRunner.parseSourceKitten(parsePath);
159
60
  if (!ast) return;
160
61
 
161
- this.currentFilePath = displayPath;
162
- resetCollections(this);
163
-
164
- try {
165
- this.fileContent = typeof contentOverride === 'string'
166
- ? contentOverride
167
- : fs.readFileSync(parsePath, 'utf8');
168
- } catch (error) {
169
- if (process.env.DEBUG) {
170
- console.debug(`[iOSASTIntelligentAnalyzer] Failed to read file content ${parsePath}: ${error.message}`);
171
- }
172
- this.fileContent = '';
173
- }
174
-
175
- const substructure = ast['key.substructure'] || [];
176
-
177
- collectAllNodes(this, substructure, null);
178
- analyzeCollectedNodes(this, displayPath);
62
+ this.analysisOrchestrator.run({
63
+ analyzer: this,
64
+ ast,
65
+ parsePath,
66
+ displayPath,
67
+ contentOverride
68
+ });
179
69
  }
180
70
 
181
71
  safeStringify(obj) {
@@ -0,0 +1,32 @@
1
+ const fs = require('fs');
2
+ const {
3
+ resetCollections,
4
+ collectAllNodes,
5
+ analyzeCollectedNodes,
6
+ } = require('../detectors/ios-ast-intelligent-strategies');
7
+
8
+ class iOSAstAnalysisOrchestrator {
9
+ run({ analyzer, ast, parsePath, displayPath, contentOverride }) {
10
+ if (!ast || !analyzer) return;
11
+
12
+ analyzer.currentFilePath = displayPath;
13
+ resetCollections(analyzer);
14
+
15
+ try {
16
+ analyzer.fileContent = typeof contentOverride === 'string'
17
+ ? contentOverride
18
+ : fs.readFileSync(parsePath, 'utf8');
19
+ } catch (error) {
20
+ if (process.env.DEBUG) {
21
+ console.debug(`[iOSAstAnalysisOrchestrator] Failed to read file content ${parsePath}: ${error.message}`);
22
+ }
23
+ analyzer.fileContent = '';
24
+ }
25
+
26
+ const substructure = ast['key.substructure'] || [];
27
+ collectAllNodes(analyzer, substructure, null);
28
+ analyzeCollectedNodes(analyzer, displayPath);
29
+ }
30
+ }
31
+
32
+ module.exports = { iOSAstAnalysisOrchestrator };