driftdetect-core 0.1.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 (221) hide show
  1. package/dist/analyzers/ast-analyzer.d.ts +251 -0
  2. package/dist/analyzers/ast-analyzer.d.ts.map +1 -0
  3. package/dist/analyzers/ast-analyzer.js +548 -0
  4. package/dist/analyzers/ast-analyzer.js.map +1 -0
  5. package/dist/analyzers/flow-analyzer.d.ts +241 -0
  6. package/dist/analyzers/flow-analyzer.d.ts.map +1 -0
  7. package/dist/analyzers/flow-analyzer.js +1219 -0
  8. package/dist/analyzers/flow-analyzer.js.map +1 -0
  9. package/dist/analyzers/index.d.ts +18 -0
  10. package/dist/analyzers/index.d.ts.map +1 -0
  11. package/dist/analyzers/index.js +19 -0
  12. package/dist/analyzers/index.js.map +1 -0
  13. package/dist/analyzers/semantic-analyzer.d.ts +252 -0
  14. package/dist/analyzers/semantic-analyzer.d.ts.map +1 -0
  15. package/dist/analyzers/semantic-analyzer.js +1182 -0
  16. package/dist/analyzers/semantic-analyzer.js.map +1 -0
  17. package/dist/analyzers/type-analyzer.d.ts +289 -0
  18. package/dist/analyzers/type-analyzer.d.ts.map +1 -0
  19. package/dist/analyzers/type-analyzer.js +1269 -0
  20. package/dist/analyzers/type-analyzer.js.map +1 -0
  21. package/dist/analyzers/types.d.ts +537 -0
  22. package/dist/analyzers/types.d.ts.map +1 -0
  23. package/dist/analyzers/types.js +11 -0
  24. package/dist/analyzers/types.js.map +1 -0
  25. package/dist/config/config-loader.d.ts +166 -0
  26. package/dist/config/config-loader.d.ts.map +1 -0
  27. package/dist/config/config-loader.js +429 -0
  28. package/dist/config/config-loader.js.map +1 -0
  29. package/dist/config/config-validator.d.ts +204 -0
  30. package/dist/config/config-validator.d.ts.map +1 -0
  31. package/dist/config/config-validator.js +632 -0
  32. package/dist/config/config-validator.js.map +1 -0
  33. package/dist/config/defaults.d.ts +8 -0
  34. package/dist/config/defaults.d.ts.map +1 -0
  35. package/dist/config/defaults.js +26 -0
  36. package/dist/config/defaults.js.map +1 -0
  37. package/dist/config/index.d.ts +10 -0
  38. package/dist/config/index.d.ts.map +1 -0
  39. package/dist/config/index.js +10 -0
  40. package/dist/config/index.js.map +1 -0
  41. package/dist/config/types.d.ts +47 -0
  42. package/dist/config/types.d.ts.map +1 -0
  43. package/dist/config/types.js +7 -0
  44. package/dist/config/types.js.map +1 -0
  45. package/dist/index.d.ts +37 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +39 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/manifest/exporter.d.ts +21 -0
  50. package/dist/manifest/exporter.d.ts.map +1 -0
  51. package/dist/manifest/exporter.js +339 -0
  52. package/dist/manifest/exporter.js.map +1 -0
  53. package/dist/manifest/index.d.ts +14 -0
  54. package/dist/manifest/index.d.ts.map +1 -0
  55. package/dist/manifest/index.js +15 -0
  56. package/dist/manifest/index.js.map +1 -0
  57. package/dist/manifest/manifest-store.d.ts +111 -0
  58. package/dist/manifest/manifest-store.d.ts.map +1 -0
  59. package/dist/manifest/manifest-store.js +418 -0
  60. package/dist/manifest/manifest-store.js.map +1 -0
  61. package/dist/manifest/types.d.ts +238 -0
  62. package/dist/manifest/types.d.ts.map +1 -0
  63. package/dist/manifest/types.js +11 -0
  64. package/dist/manifest/types.js.map +1 -0
  65. package/dist/matcher/confidence-scorer.d.ts +188 -0
  66. package/dist/matcher/confidence-scorer.d.ts.map +1 -0
  67. package/dist/matcher/confidence-scorer.js +302 -0
  68. package/dist/matcher/confidence-scorer.js.map +1 -0
  69. package/dist/matcher/index.d.ts +24 -0
  70. package/dist/matcher/index.d.ts.map +1 -0
  71. package/dist/matcher/index.js +26 -0
  72. package/dist/matcher/index.js.map +1 -0
  73. package/dist/matcher/outlier-detector.d.ts +252 -0
  74. package/dist/matcher/outlier-detector.d.ts.map +1 -0
  75. package/dist/matcher/outlier-detector.js +544 -0
  76. package/dist/matcher/outlier-detector.js.map +1 -0
  77. package/dist/matcher/pattern-matcher.d.ts +169 -0
  78. package/dist/matcher/pattern-matcher.d.ts.map +1 -0
  79. package/dist/matcher/pattern-matcher.js +692 -0
  80. package/dist/matcher/pattern-matcher.js.map +1 -0
  81. package/dist/matcher/types.d.ts +476 -0
  82. package/dist/matcher/types.d.ts.map +1 -0
  83. package/dist/matcher/types.js +36 -0
  84. package/dist/matcher/types.js.map +1 -0
  85. package/dist/parsers/base-parser.d.ts +282 -0
  86. package/dist/parsers/base-parser.d.ts.map +1 -0
  87. package/dist/parsers/base-parser.js +421 -0
  88. package/dist/parsers/base-parser.js.map +1 -0
  89. package/dist/parsers/css-parser.d.ts +225 -0
  90. package/dist/parsers/css-parser.d.ts.map +1 -0
  91. package/dist/parsers/css-parser.js +477 -0
  92. package/dist/parsers/css-parser.js.map +1 -0
  93. package/dist/parsers/index.d.ts +15 -0
  94. package/dist/parsers/index.d.ts.map +1 -0
  95. package/dist/parsers/index.js +15 -0
  96. package/dist/parsers/index.js.map +1 -0
  97. package/dist/parsers/json-parser.d.ts +219 -0
  98. package/dist/parsers/json-parser.d.ts.map +1 -0
  99. package/dist/parsers/json-parser.js +602 -0
  100. package/dist/parsers/json-parser.js.map +1 -0
  101. package/dist/parsers/markdown-parser.d.ts +276 -0
  102. package/dist/parsers/markdown-parser.d.ts.map +1 -0
  103. package/dist/parsers/markdown-parser.js +731 -0
  104. package/dist/parsers/markdown-parser.js.map +1 -0
  105. package/dist/parsers/parser-manager.d.ts +294 -0
  106. package/dist/parsers/parser-manager.d.ts.map +1 -0
  107. package/dist/parsers/parser-manager.js +738 -0
  108. package/dist/parsers/parser-manager.js.map +1 -0
  109. package/dist/parsers/python-parser.d.ts +204 -0
  110. package/dist/parsers/python-parser.d.ts.map +1 -0
  111. package/dist/parsers/python-parser.js +517 -0
  112. package/dist/parsers/python-parser.js.map +1 -0
  113. package/dist/parsers/types.d.ts +43 -0
  114. package/dist/parsers/types.d.ts.map +1 -0
  115. package/dist/parsers/types.js +7 -0
  116. package/dist/parsers/types.js.map +1 -0
  117. package/dist/parsers/typescript-parser.d.ts +264 -0
  118. package/dist/parsers/typescript-parser.d.ts.map +1 -0
  119. package/dist/parsers/typescript-parser.js +658 -0
  120. package/dist/parsers/typescript-parser.js.map +1 -0
  121. package/dist/rules/evaluator.d.ts +305 -0
  122. package/dist/rules/evaluator.d.ts.map +1 -0
  123. package/dist/rules/evaluator.js +579 -0
  124. package/dist/rules/evaluator.js.map +1 -0
  125. package/dist/rules/index.d.ts +13 -0
  126. package/dist/rules/index.d.ts.map +1 -0
  127. package/dist/rules/index.js +13 -0
  128. package/dist/rules/index.js.map +1 -0
  129. package/dist/rules/quick-fix-generator.d.ts +334 -0
  130. package/dist/rules/quick-fix-generator.d.ts.map +1 -0
  131. package/dist/rules/quick-fix-generator.js +1075 -0
  132. package/dist/rules/quick-fix-generator.js.map +1 -0
  133. package/dist/rules/rule-engine.d.ts +241 -0
  134. package/dist/rules/rule-engine.d.ts.map +1 -0
  135. package/dist/rules/rule-engine.js +585 -0
  136. package/dist/rules/rule-engine.js.map +1 -0
  137. package/dist/rules/severity-manager.d.ts +394 -0
  138. package/dist/rules/severity-manager.d.ts.map +1 -0
  139. package/dist/rules/severity-manager.js +619 -0
  140. package/dist/rules/severity-manager.js.map +1 -0
  141. package/dist/rules/types.d.ts +370 -0
  142. package/dist/rules/types.d.ts.map +1 -0
  143. package/dist/rules/types.js +133 -0
  144. package/dist/rules/types.js.map +1 -0
  145. package/dist/rules/variant-manager.d.ts +388 -0
  146. package/dist/rules/variant-manager.d.ts.map +1 -0
  147. package/dist/rules/variant-manager.js +777 -0
  148. package/dist/rules/variant-manager.js.map +1 -0
  149. package/dist/scanner/change-detector.d.ts +164 -0
  150. package/dist/scanner/change-detector.d.ts.map +1 -0
  151. package/dist/scanner/change-detector.js +263 -0
  152. package/dist/scanner/change-detector.js.map +1 -0
  153. package/dist/scanner/dependency-graph.d.ts +270 -0
  154. package/dist/scanner/dependency-graph.d.ts.map +1 -0
  155. package/dist/scanner/dependency-graph.js +436 -0
  156. package/dist/scanner/dependency-graph.js.map +1 -0
  157. package/dist/scanner/file-walker.d.ts +127 -0
  158. package/dist/scanner/file-walker.d.ts.map +1 -0
  159. package/dist/scanner/file-walker.js +526 -0
  160. package/dist/scanner/file-walker.js.map +1 -0
  161. package/dist/scanner/index.d.ts +12 -0
  162. package/dist/scanner/index.d.ts.map +1 -0
  163. package/dist/scanner/index.js +12 -0
  164. package/dist/scanner/index.js.map +1 -0
  165. package/dist/scanner/types.d.ts +218 -0
  166. package/dist/scanner/types.d.ts.map +1 -0
  167. package/dist/scanner/types.js +10 -0
  168. package/dist/scanner/types.js.map +1 -0
  169. package/dist/scanner/worker-pool.d.ts +317 -0
  170. package/dist/scanner/worker-pool.d.ts.map +1 -0
  171. package/dist/scanner/worker-pool.js +571 -0
  172. package/dist/scanner/worker-pool.js.map +1 -0
  173. package/dist/store/cache-manager.d.ts +179 -0
  174. package/dist/store/cache-manager.d.ts.map +1 -0
  175. package/dist/store/cache-manager.js +391 -0
  176. package/dist/store/cache-manager.js.map +1 -0
  177. package/dist/store/history-store.d.ts +314 -0
  178. package/dist/store/history-store.d.ts.map +1 -0
  179. package/dist/store/history-store.js +707 -0
  180. package/dist/store/history-store.js.map +1 -0
  181. package/dist/store/index.d.ts +20 -0
  182. package/dist/store/index.d.ts.map +1 -0
  183. package/dist/store/index.js +26 -0
  184. package/dist/store/index.js.map +1 -0
  185. package/dist/store/lock-file-manager.d.ts +202 -0
  186. package/dist/store/lock-file-manager.d.ts.map +1 -0
  187. package/dist/store/lock-file-manager.js +475 -0
  188. package/dist/store/lock-file-manager.js.map +1 -0
  189. package/dist/store/pattern-store.d.ts +289 -0
  190. package/dist/store/pattern-store.d.ts.map +1 -0
  191. package/dist/store/pattern-store.js +936 -0
  192. package/dist/store/pattern-store.js.map +1 -0
  193. package/dist/store/schema-validator.d.ts +159 -0
  194. package/dist/store/schema-validator.d.ts.map +1 -0
  195. package/dist/store/schema-validator.js +1096 -0
  196. package/dist/store/schema-validator.js.map +1 -0
  197. package/dist/store/types.d.ts +585 -0
  198. package/dist/store/types.d.ts.map +1 -0
  199. package/dist/store/types.js +82 -0
  200. package/dist/store/types.js.map +1 -0
  201. package/dist/types/analysis.d.ts +19 -0
  202. package/dist/types/analysis.d.ts.map +1 -0
  203. package/dist/types/analysis.js +5 -0
  204. package/dist/types/analysis.js.map +1 -0
  205. package/dist/types/common.d.ts +7 -0
  206. package/dist/types/common.d.ts.map +1 -0
  207. package/dist/types/common.js +5 -0
  208. package/dist/types/common.js.map +1 -0
  209. package/dist/types/index.d.ts +12 -0
  210. package/dist/types/index.d.ts.map +1 -0
  211. package/dist/types/index.js +10 -0
  212. package/dist/types/index.js.map +1 -0
  213. package/dist/types/patterns.d.ts +40 -0
  214. package/dist/types/patterns.d.ts.map +1 -0
  215. package/dist/types/patterns.js +7 -0
  216. package/dist/types/patterns.js.map +1 -0
  217. package/dist/types/violations.d.ts +7 -0
  218. package/dist/types/violations.d.ts.map +1 -0
  219. package/dist/types/violations.js +7 -0
  220. package/dist/types/violations.js.map +1 -0
  221. package/package.json +46 -0
@@ -0,0 +1,1075 @@
1
+ /**
2
+ * Quick Fix Generator - Code transformation generation for violations
3
+ *
4
+ * Generates code transformations (quick fixes) for pattern violations.
5
+ * Supports multiple fix types: replace, wrap, extract, import, rename, move, delete.
6
+ * Provides preview of changes before applying.
7
+ *
8
+ * @requirements 25.1 - THE Quick_Fix_System SHALL generate code transformations for fixable violations
9
+ * @requirements 25.2 - THE Quick_Fix SHALL include a preview of the change before applying
10
+ * @requirements 25.3 - THE Quick_Fix SHALL support fix types: replace, wrap, extract, import, rename, move, delete
11
+ * @requirements 25.4 - WHEN multiple fixes are available, THE Quick_Fix_System SHALL rank by confidence
12
+ * @requirements 25.5 - THE Quick_Fix_System SHALL mark the preferred fix for one-click application
13
+ */
14
+ import { createTextEdit, createInsertEdit, createDeleteEdit, createWorkspaceEdit, createPosition, } from './types.js';
15
+ /**
16
+ * Default QuickFixGenerator configuration
17
+ */
18
+ export const DEFAULT_QUICK_FIX_GENERATOR_CONFIG = {
19
+ minConfidence: 0.5,
20
+ maxFixesPerViolation: 5,
21
+ generatePreviews: true,
22
+ validateFixes: true,
23
+ };
24
+ // ============================================================================
25
+ // Replace Fix Strategy
26
+ // ============================================================================
27
+ /**
28
+ * Strategy for generating replace fixes
29
+ * Replaces text at a specific range with new text
30
+ */
31
+ export class ReplaceFixStrategy {
32
+ type = 'replace';
33
+ canHandle(violation) {
34
+ // Replace can handle most violations where we know what to replace with
35
+ return violation.expected !== undefined && violation.expected.length > 0;
36
+ }
37
+ getConfidence(context) {
38
+ // Higher confidence if expected and actual are clearly different
39
+ if (context.expected === context.actual) {
40
+ return 0;
41
+ }
42
+ // Base confidence for replace operations
43
+ return 0.8;
44
+ }
45
+ generate(context) {
46
+ const { violation, expected } = context;
47
+ if (!expected || expected.length === 0) {
48
+ return null;
49
+ }
50
+ const edit = createTextEdit(violation.range, expected);
51
+ const workspaceEdit = createWorkspaceEdit(violation.file, [edit]);
52
+ return {
53
+ title: `Replace with: ${this.truncateText(expected, 50)}`,
54
+ kind: 'quickfix',
55
+ edit: workspaceEdit,
56
+ isPreferred: true,
57
+ confidence: this.getConfidence(context),
58
+ preview: this.generatePreview(context, expected),
59
+ };
60
+ }
61
+ truncateText(text, maxLength) {
62
+ if (text.length <= maxLength) {
63
+ return text;
64
+ }
65
+ return text.substring(0, maxLength - 3) + '...';
66
+ }
67
+ generatePreview(context, newText) {
68
+ return `Replace "${this.truncateText(context.actual, 30)}" with "${this.truncateText(newText, 30)}"`;
69
+ }
70
+ }
71
+ // ============================================================================
72
+ // Wrap Fix Strategy
73
+ // ============================================================================
74
+ /**
75
+ * Strategy for generating wrap fixes
76
+ * Wraps code with additional structure (e.g., try/catch, function wrapper)
77
+ */
78
+ export class WrapFixStrategy {
79
+ type = 'wrap';
80
+ canHandle(violation) {
81
+ // Wrap can handle violations that suggest wrapping code
82
+ const wrapKeywords = ['wrap', 'surround', 'enclose', 'try', 'catch', 'async'];
83
+ const message = violation.message.toLowerCase();
84
+ return wrapKeywords.some(keyword => message.includes(keyword));
85
+ }
86
+ getConfidence(_context) {
87
+ // Wrap operations have moderate confidence
88
+ return 0.7;
89
+ }
90
+ generate(context) {
91
+ const { violation, content } = context;
92
+ // Extract the code to wrap
93
+ const codeToWrap = this.extractCode(content, violation.range);
94
+ if (!codeToWrap) {
95
+ return null;
96
+ }
97
+ // Determine wrap type based on violation message
98
+ const wrapType = this.determineWrapType(violation);
99
+ const wrappedCode = this.wrapCode(codeToWrap, wrapType);
100
+ const edit = createTextEdit(violation.range, wrappedCode);
101
+ const workspaceEdit = createWorkspaceEdit(violation.file, [edit]);
102
+ return {
103
+ title: `Wrap with ${wrapType}`,
104
+ kind: 'refactor',
105
+ edit: workspaceEdit,
106
+ isPreferred: false,
107
+ confidence: this.getConfidence(context),
108
+ preview: `Wrap code with ${wrapType} block`,
109
+ };
110
+ }
111
+ extractCode(content, range) {
112
+ const lines = content.split('\n');
113
+ const startLine = range.start.line;
114
+ const endLine = range.end.line;
115
+ if (startLine < 0 || endLine >= lines.length) {
116
+ return null;
117
+ }
118
+ if (startLine === endLine) {
119
+ const line = lines[startLine];
120
+ if (line === undefined)
121
+ return null;
122
+ return line.substring(range.start.character, range.end.character);
123
+ }
124
+ const extractedLines = [];
125
+ for (let i = startLine; i <= endLine; i++) {
126
+ const line = lines[i];
127
+ if (line === undefined)
128
+ continue;
129
+ if (i === startLine) {
130
+ extractedLines.push(line.substring(range.start.character));
131
+ }
132
+ else if (i === endLine) {
133
+ extractedLines.push(line.substring(0, range.end.character));
134
+ }
135
+ else {
136
+ extractedLines.push(line);
137
+ }
138
+ }
139
+ return extractedLines.join('\n');
140
+ }
141
+ determineWrapType(violation) {
142
+ const message = violation.message.toLowerCase();
143
+ if (message.includes('try') || message.includes('catch') || message.includes('error')) {
144
+ return 'try-catch';
145
+ }
146
+ if (message.includes('async') || message.includes('await')) {
147
+ return 'async';
148
+ }
149
+ if (message.includes('function')) {
150
+ return 'function';
151
+ }
152
+ return 'block';
153
+ }
154
+ wrapCode(code, wrapType) {
155
+ const indent = this.detectIndent(code);
156
+ switch (wrapType) {
157
+ case 'try-catch':
158
+ return `try {\n${indent} ${code}\n${indent}} catch (error) {\n${indent} throw error;\n${indent}}`;
159
+ case 'async':
160
+ return `(async () => {\n${indent} ${code}\n${indent}})()`;
161
+ case 'function':
162
+ return `function wrapper() {\n${indent} ${code}\n${indent}}`;
163
+ default:
164
+ return `{\n${indent} ${code}\n${indent}}`;
165
+ }
166
+ }
167
+ detectIndent(code) {
168
+ const match = code.match(/^(\s*)/);
169
+ return match && match[1] ? match[1] : '';
170
+ }
171
+ }
172
+ // ============================================================================
173
+ // Extract Fix Strategy
174
+ // ============================================================================
175
+ /**
176
+ * Strategy for generating extract fixes
177
+ * Extracts code into a new location (e.g., extract function, extract variable)
178
+ */
179
+ export class ExtractFixStrategy {
180
+ type = 'extract';
181
+ canHandle(violation) {
182
+ // Extract can handle violations suggesting code extraction
183
+ const extractKeywords = ['extract', 'refactor', 'duplicate', 'repeated', 'abstract'];
184
+ const message = violation.message.toLowerCase();
185
+ return extractKeywords.some(keyword => message.includes(keyword));
186
+ }
187
+ getConfidence(_context) {
188
+ // Extract operations have moderate confidence
189
+ return 0.65;
190
+ }
191
+ generate(context) {
192
+ const { violation, content } = context;
193
+ // Extract the code to extract
194
+ const codeToExtract = this.extractCode(content, violation.range);
195
+ if (!codeToExtract) {
196
+ return null;
197
+ }
198
+ // Generate extracted function/variable
199
+ const extractedName = this.generateExtractedName(violation);
200
+ const { extractedCode, replacement } = this.createExtraction(codeToExtract, extractedName);
201
+ // Create edits: replace original with reference, add extracted code
202
+ const replaceEdit = createTextEdit(violation.range, replacement);
203
+ // Insert extracted code at the beginning of the file (simplified)
204
+ const insertPosition = createPosition(0, 0);
205
+ const insertEdit = createInsertEdit(insertPosition, extractedCode + '\n\n');
206
+ const workspaceEdit = {
207
+ changes: {
208
+ [violation.file]: [insertEdit, replaceEdit],
209
+ },
210
+ };
211
+ return {
212
+ title: `Extract to ${extractedName}`,
213
+ kind: 'refactor',
214
+ edit: workspaceEdit,
215
+ isPreferred: false,
216
+ confidence: this.getConfidence(context),
217
+ preview: `Extract code to function "${extractedName}"`,
218
+ };
219
+ }
220
+ extractCode(content, range) {
221
+ const lines = content.split('\n');
222
+ const startLine = range.start.line;
223
+ const endLine = range.end.line;
224
+ if (startLine < 0 || endLine >= lines.length) {
225
+ return null;
226
+ }
227
+ if (startLine === endLine) {
228
+ const line = lines[startLine];
229
+ if (line === undefined)
230
+ return null;
231
+ return line.substring(range.start.character, range.end.character);
232
+ }
233
+ const extractedLines = [];
234
+ for (let i = startLine; i <= endLine; i++) {
235
+ const line = lines[i];
236
+ if (line === undefined)
237
+ continue;
238
+ if (i === startLine) {
239
+ extractedLines.push(line.substring(range.start.character));
240
+ }
241
+ else if (i === endLine) {
242
+ extractedLines.push(line.substring(0, range.end.character));
243
+ }
244
+ else {
245
+ extractedLines.push(line);
246
+ }
247
+ }
248
+ return extractedLines.join('\n');
249
+ }
250
+ generateExtractedName(violation) {
251
+ // Generate a name based on the pattern or violation
252
+ const patternId = violation.patternId;
253
+ const sanitized = patternId.replace(/[^a-zA-Z0-9]/g, '_');
254
+ return `extracted_${sanitized}`;
255
+ }
256
+ createExtraction(code, name) {
257
+ // Create a function extraction
258
+ const extractedCode = `function ${name}() {\n ${code}\n}`;
259
+ const replacement = `${name}()`;
260
+ return { extractedCode, replacement };
261
+ }
262
+ }
263
+ // ============================================================================
264
+ // Import Fix Strategy
265
+ // ============================================================================
266
+ /**
267
+ * Strategy for generating import fixes
268
+ * Adds import statements for missing dependencies
269
+ */
270
+ export class ImportFixStrategy {
271
+ type = 'import';
272
+ canHandle(violation) {
273
+ // Import can handle violations about missing imports
274
+ const importKeywords = ['import', 'require', 'missing', 'undefined', 'not found', 'module'];
275
+ const message = violation.message.toLowerCase();
276
+ return importKeywords.some(keyword => message.includes(keyword));
277
+ }
278
+ getConfidence(_context) {
279
+ // Import operations have high confidence when we know what to import
280
+ return 0.85;
281
+ }
282
+ generate(context) {
283
+ const { violation, expected } = context;
284
+ // Try to extract import information from expected
285
+ const importStatement = this.generateImportStatement(expected, violation);
286
+ if (!importStatement) {
287
+ return null;
288
+ }
289
+ // Insert import at the top of the file
290
+ const insertPosition = createPosition(0, 0);
291
+ const insertEdit = createInsertEdit(insertPosition, importStatement + '\n');
292
+ const workspaceEdit = createWorkspaceEdit(violation.file, [insertEdit]);
293
+ return {
294
+ title: `Add import: ${this.truncateText(importStatement, 50)}`,
295
+ kind: 'quickfix',
296
+ edit: workspaceEdit,
297
+ isPreferred: true,
298
+ confidence: this.getConfidence(context),
299
+ preview: `Add import statement at top of file`,
300
+ };
301
+ }
302
+ generateImportStatement(expected, violation) {
303
+ // Try to parse expected as an import statement
304
+ if (expected.startsWith('import ')) {
305
+ return expected;
306
+ }
307
+ // Try to extract module name from violation message
308
+ const moduleMatch = violation.message.match(/['"]([^'"]+)['"]/);
309
+ if (moduleMatch && moduleMatch[1]) {
310
+ return `import { } from '${moduleMatch[1]}';`;
311
+ }
312
+ // Try to extract from expected
313
+ if (expected.includes('/') || expected.includes('@')) {
314
+ return `import { } from '${expected}';`;
315
+ }
316
+ return null;
317
+ }
318
+ truncateText(text, maxLength) {
319
+ if (text.length <= maxLength) {
320
+ return text;
321
+ }
322
+ return text.substring(0, maxLength - 3) + '...';
323
+ }
324
+ }
325
+ // ============================================================================
326
+ // Rename Fix Strategy
327
+ // ============================================================================
328
+ /**
329
+ * Strategy for generating rename fixes
330
+ * Renames symbols to match naming conventions
331
+ */
332
+ export class RenameFixStrategy {
333
+ type = 'rename';
334
+ canHandle(violation) {
335
+ // Rename can handle violations about naming conventions
336
+ const renameKeywords = ['rename', 'naming', 'convention', 'case', 'camel', 'pascal', 'snake', 'kebab'];
337
+ const message = violation.message.toLowerCase();
338
+ return renameKeywords.some(keyword => message.includes(keyword));
339
+ }
340
+ getConfidence(_context) {
341
+ // Rename operations have high confidence for naming violations
342
+ return 0.9;
343
+ }
344
+ generate(context) {
345
+ const { violation, expected, actual } = context;
346
+ if (!expected || expected === actual) {
347
+ return null;
348
+ }
349
+ // Generate the new name based on expected pattern
350
+ const newName = this.generateNewName(actual, expected, violation);
351
+ if (!newName || newName === actual) {
352
+ return null;
353
+ }
354
+ const edit = createTextEdit(violation.range, newName);
355
+ const workspaceEdit = createWorkspaceEdit(violation.file, [edit]);
356
+ return {
357
+ title: `Rename to "${newName}"`,
358
+ kind: 'quickfix',
359
+ edit: workspaceEdit,
360
+ isPreferred: true,
361
+ confidence: this.getConfidence(context),
362
+ preview: `Rename "${actual}" to "${newName}"`,
363
+ };
364
+ }
365
+ generateNewName(actual, expected, violation) {
366
+ const message = violation.message.toLowerCase();
367
+ // If expected is a specific name, use it
368
+ if (!expected.includes('case') && !expected.includes('convention')) {
369
+ return expected;
370
+ }
371
+ // Convert based on naming convention
372
+ if (message.includes('camelcase') || message.includes('camel case')) {
373
+ return this.toCamelCase(actual);
374
+ }
375
+ if (message.includes('pascalcase') || message.includes('pascal case')) {
376
+ return this.toPascalCase(actual);
377
+ }
378
+ if (message.includes('snake_case') || message.includes('snake case')) {
379
+ return this.toSnakeCase(actual);
380
+ }
381
+ if (message.includes('kebab-case') || message.includes('kebab case')) {
382
+ return this.toKebabCase(actual);
383
+ }
384
+ return expected;
385
+ }
386
+ toCamelCase(str) {
387
+ return str
388
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
389
+ .replace(/^[A-Z]/, c => c.toLowerCase());
390
+ }
391
+ toPascalCase(str) {
392
+ return str
393
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
394
+ .replace(/^[a-z]/, c => c.toUpperCase());
395
+ }
396
+ toSnakeCase(str) {
397
+ return str
398
+ .replace(/([A-Z])/g, '_$1')
399
+ .replace(/[-\s]+/g, '_')
400
+ .toLowerCase()
401
+ .replace(/^_/, '');
402
+ }
403
+ toKebabCase(str) {
404
+ return str
405
+ .replace(/([A-Z])/g, '-$1')
406
+ .replace(/[_\s]+/g, '-')
407
+ .toLowerCase()
408
+ .replace(/^-/, '');
409
+ }
410
+ }
411
+ // ============================================================================
412
+ // Move Fix Strategy
413
+ // ============================================================================
414
+ /**
415
+ * Strategy for generating move fixes
416
+ * Moves code to a different location (e.g., different file, different position)
417
+ */
418
+ export class MoveFixStrategy {
419
+ type = 'move';
420
+ canHandle(violation) {
421
+ // Move can handle violations about code location
422
+ const moveKeywords = ['move', 'relocate', 'location', 'position', 'place', 'organize'];
423
+ const message = violation.message.toLowerCase();
424
+ return moveKeywords.some(keyword => message.includes(keyword));
425
+ }
426
+ getConfidence(_context) {
427
+ // Move operations have lower confidence due to complexity
428
+ return 0.6;
429
+ }
430
+ generate(context) {
431
+ const { violation, content, expected } = context;
432
+ // Extract the code to move
433
+ const codeToMove = this.extractCode(content, violation.range);
434
+ if (!codeToMove) {
435
+ return null;
436
+ }
437
+ // Determine target location
438
+ const targetLocation = this.determineTargetLocation(violation, expected);
439
+ if (!targetLocation) {
440
+ return null;
441
+ }
442
+ // Create edits: delete from original, insert at target
443
+ const deleteEdit = createDeleteEdit(violation.range);
444
+ const insertEdit = createInsertEdit(targetLocation, codeToMove);
445
+ const workspaceEdit = {
446
+ changes: {
447
+ [violation.file]: [deleteEdit, insertEdit],
448
+ },
449
+ };
450
+ return {
451
+ title: `Move code to line ${targetLocation.line + 1}`,
452
+ kind: 'refactor',
453
+ edit: workspaceEdit,
454
+ isPreferred: false,
455
+ confidence: this.getConfidence(context),
456
+ preview: `Move code from line ${violation.range.start.line + 1} to line ${targetLocation.line + 1}`,
457
+ };
458
+ }
459
+ extractCode(content, range) {
460
+ const lines = content.split('\n');
461
+ const startLine = range.start.line;
462
+ const endLine = range.end.line;
463
+ if (startLine < 0 || endLine >= lines.length) {
464
+ return null;
465
+ }
466
+ if (startLine === endLine) {
467
+ const line = lines[startLine];
468
+ if (line === undefined)
469
+ return null;
470
+ return line.substring(range.start.character, range.end.character);
471
+ }
472
+ const extractedLines = [];
473
+ for (let i = startLine; i <= endLine; i++) {
474
+ const line = lines[i];
475
+ if (line === undefined)
476
+ continue;
477
+ if (i === startLine) {
478
+ extractedLines.push(line.substring(range.start.character));
479
+ }
480
+ else if (i === endLine) {
481
+ extractedLines.push(line.substring(0, range.end.character));
482
+ }
483
+ else {
484
+ extractedLines.push(line);
485
+ }
486
+ }
487
+ return extractedLines.join('\n');
488
+ }
489
+ determineTargetLocation(_violation, expected) {
490
+ // Try to parse target line from expected
491
+ const lineMatch = expected.match(/line\s*(\d+)/i);
492
+ if (lineMatch && lineMatch[1]) {
493
+ const targetLine = parseInt(lineMatch[1], 10) - 1; // Convert to 0-indexed
494
+ return createPosition(targetLine, 0);
495
+ }
496
+ // Default: move to beginning of file
497
+ return createPosition(0, 0);
498
+ }
499
+ }
500
+ // ============================================================================
501
+ // Delete Fix Strategy
502
+ // ============================================================================
503
+ /**
504
+ * Strategy for generating delete fixes
505
+ * Deletes code that should be removed
506
+ */
507
+ export class DeleteFixStrategy {
508
+ type = 'delete';
509
+ canHandle(violation) {
510
+ // Delete can handle violations about unnecessary code
511
+ const deleteKeywords = ['delete', 'remove', 'unused', 'unnecessary', 'redundant', 'dead code'];
512
+ const message = violation.message.toLowerCase();
513
+ return deleteKeywords.some(keyword => message.includes(keyword));
514
+ }
515
+ getConfidence(_context) {
516
+ // Delete operations have moderate confidence
517
+ return 0.75;
518
+ }
519
+ generate(context) {
520
+ const { violation } = context;
521
+ const deleteEdit = createDeleteEdit(violation.range);
522
+ const workspaceEdit = createWorkspaceEdit(violation.file, [deleteEdit]);
523
+ return {
524
+ title: 'Delete code',
525
+ kind: 'quickfix',
526
+ edit: workspaceEdit,
527
+ isPreferred: false,
528
+ confidence: this.getConfidence(context),
529
+ preview: `Delete code at lines ${violation.range.start.line + 1}-${violation.range.end.line + 1}`,
530
+ };
531
+ }
532
+ }
533
+ // ============================================================================
534
+ // Quick Fix Generator Class
535
+ // ============================================================================
536
+ /**
537
+ * QuickFixGenerator class for generating code transformations for violations.
538
+ *
539
+ * The generator:
540
+ * - Takes violations and generates appropriate quick fixes
541
+ * - Supports multiple fix types: replace, wrap, extract, import, rename, move, delete
542
+ * - Ranks fixes by confidence and marks preferred fix
543
+ * - Provides preview of changes before applying
544
+ *
545
+ * @requirements 25.1 - Generate code transformations for fixable violations
546
+ * @requirements 25.3 - Support fix types: replace, wrap, extract, import, rename, move, delete
547
+ * @requirements 25.4 - Rank fixes by confidence
548
+ * @requirements 25.5 - Mark preferred fix for one-click application
549
+ */
550
+ export class QuickFixGenerator {
551
+ config;
552
+ strategies;
553
+ fixIdCounter;
554
+ /**
555
+ * Create a new QuickFixGenerator instance.
556
+ *
557
+ * @param config - Optional configuration options
558
+ */
559
+ constructor(config) {
560
+ this.config = {
561
+ ...DEFAULT_QUICK_FIX_GENERATOR_CONFIG,
562
+ ...config,
563
+ };
564
+ // Initialize all fix strategies
565
+ this.strategies = [
566
+ new ReplaceFixStrategy(),
567
+ new WrapFixStrategy(),
568
+ new ExtractFixStrategy(),
569
+ new ImportFixStrategy(),
570
+ new RenameFixStrategy(),
571
+ new MoveFixStrategy(),
572
+ new DeleteFixStrategy(),
573
+ ];
574
+ this.fixIdCounter = 0;
575
+ }
576
+ /**
577
+ * Generate quick fixes for a violation.
578
+ *
579
+ * @param violation - The violation to generate fixes for
580
+ * @param content - The file content
581
+ * @returns Fix generation result with ranked fixes
582
+ *
583
+ * @requirements 25.1 - Generate code transformations
584
+ * @requirements 25.4 - Rank by confidence
585
+ */
586
+ generateFixes(violation, content) {
587
+ const errors = [];
588
+ const fixes = [];
589
+ const context = {
590
+ violation,
591
+ content,
592
+ expected: violation.expected,
593
+ actual: violation.actual,
594
+ };
595
+ // Try each strategy
596
+ for (const strategy of this.strategies) {
597
+ try {
598
+ if (strategy.canHandle(violation)) {
599
+ const fix = strategy.generate(context);
600
+ if (fix && fix.confidence >= this.config.minConfidence) {
601
+ const fixWithMetadata = this.addMetadata(fix, violation, strategy.type);
602
+ fixes.push(fixWithMetadata);
603
+ }
604
+ }
605
+ }
606
+ catch (error) {
607
+ errors.push(`Strategy ${strategy.type} failed: ${error instanceof Error ? error.message : String(error)}`);
608
+ }
609
+ }
610
+ // Sort by confidence (highest first)
611
+ fixes.sort((a, b) => b.confidence - a.confidence);
612
+ // Limit number of fixes
613
+ const limitedFixes = fixes.slice(0, this.config.maxFixesPerViolation);
614
+ // Mark preferred fix
615
+ if (limitedFixes.length > 0) {
616
+ const firstFix = limitedFixes[0];
617
+ if (firstFix) {
618
+ firstFix.isPreferred = true;
619
+ }
620
+ for (let i = 1; i < limitedFixes.length; i++) {
621
+ const fix = limitedFixes[i];
622
+ if (fix) {
623
+ fix.isPreferred = false;
624
+ }
625
+ }
626
+ }
627
+ const result = {
628
+ violationId: violation.id,
629
+ fixes: limitedFixes,
630
+ hasFixs: limitedFixes.length > 0,
631
+ errors,
632
+ };
633
+ if (limitedFixes.length > 0 && limitedFixes[0]) {
634
+ result.preferredFix = limitedFixes[0];
635
+ }
636
+ return result;
637
+ }
638
+ /**
639
+ * Generate quick fixes for multiple violations.
640
+ *
641
+ * @param violations - Array of violations
642
+ * @param content - The file content
643
+ * @returns Array of fix generation results
644
+ */
645
+ generateFixesForAll(violations, content) {
646
+ return violations.map(violation => this.generateFixes(violation, content));
647
+ }
648
+ /**
649
+ * Generate a specific type of fix for a violation.
650
+ *
651
+ * @param violation - The violation to fix
652
+ * @param content - The file content
653
+ * @param fixType - The type of fix to generate
654
+ * @returns The generated fix or null if not applicable
655
+ */
656
+ generateFixOfType(violation, content, fixType) {
657
+ const strategy = this.strategies.find(s => s.type === fixType);
658
+ if (!strategy) {
659
+ return null;
660
+ }
661
+ const context = {
662
+ violation,
663
+ content,
664
+ expected: violation.expected,
665
+ actual: violation.actual,
666
+ };
667
+ if (!strategy.canHandle(violation)) {
668
+ return null;
669
+ }
670
+ return strategy.generate(context);
671
+ }
672
+ /**
673
+ * Generate a preview of a fix before applying.
674
+ *
675
+ * @param fix - The quick fix to preview
676
+ * @param content - The original file content
677
+ * @returns Preview string showing the change
678
+ *
679
+ * @requirements 25.2 - Include preview of change before applying
680
+ */
681
+ generatePreview(fix, content) {
682
+ if (fix.preview) {
683
+ return fix.preview;
684
+ }
685
+ // Generate a diff-like preview
686
+ const lines = [];
687
+ const fileEdits = Object.entries(fix.edit.changes);
688
+ for (const [file, edits] of fileEdits) {
689
+ lines.push(`File: ${file}`);
690
+ lines.push('---');
691
+ for (const edit of edits) {
692
+ const originalText = this.extractTextFromRange(content, edit.range);
693
+ if (edit.newText === '') {
694
+ lines.push(`- ${originalText}`);
695
+ }
696
+ else if (originalText === '') {
697
+ lines.push(`+ ${edit.newText}`);
698
+ }
699
+ else {
700
+ lines.push(`- ${originalText}`);
701
+ lines.push(`+ ${edit.newText}`);
702
+ }
703
+ }
704
+ }
705
+ return lines.join('\n');
706
+ }
707
+ /**
708
+ * Apply a fix to content and return the result.
709
+ *
710
+ * This method is idempotent: applying the same fix twice will result in
711
+ * no additional changes after the first application.
712
+ *
713
+ * @param fix - The quick fix to apply
714
+ * @param content - The original file content
715
+ * @returns The modified content after applying the fix
716
+ *
717
+ * @requirements 25.1 - Generate code transformations for fixable violations
718
+ */
719
+ applyFix(fix, content) {
720
+ // Get edits for the file (assuming single file for now)
721
+ const fileEdits = Object.values(fix.edit.changes)[0];
722
+ if (!fileEdits || fileEdits.length === 0) {
723
+ return content;
724
+ }
725
+ // Sort edits by position (reverse order to apply from end to start)
726
+ const sortedEdits = [...fileEdits].sort((a, b) => {
727
+ if (a.range.start.line !== b.range.start.line) {
728
+ return b.range.start.line - a.range.start.line;
729
+ }
730
+ return b.range.start.character - a.range.start.character;
731
+ });
732
+ // Apply all edits
733
+ let result = content;
734
+ for (const edit of sortedEdits) {
735
+ if (edit.newText === '') {
736
+ // Delete operation: check if the text at the range is empty
737
+ // If it is, the delete has already been applied
738
+ const textAtRange = this.extractTextFromRange(result, edit.range);
739
+ if (textAtRange === '') {
740
+ // Range is already empty, skip this edit
741
+ continue;
742
+ }
743
+ }
744
+ else {
745
+ // Replace/insert operation: check if the text at the start position
746
+ // already matches the newText. If so, skip this edit.
747
+ const textAtStart = this.extractTextFromPosition(result, edit.range.start, edit.newText.length);
748
+ if (textAtStart === edit.newText) {
749
+ // Text already matches, skip this edit
750
+ continue;
751
+ }
752
+ }
753
+ result = this.applyTextEdit(result, edit);
754
+ }
755
+ return result;
756
+ }
757
+ /**
758
+ * Extract text from a position for a given length.
759
+ * Used for idempotence checking.
760
+ */
761
+ extractTextFromPosition(content, start, length) {
762
+ const lines = content.split('\n');
763
+ if (start.line < 0 || start.line >= lines.length) {
764
+ return '';
765
+ }
766
+ let result = '';
767
+ let currentLine = start.line;
768
+ let currentChar = start.character;
769
+ let remaining = length;
770
+ while (remaining > 0 && currentLine < lines.length) {
771
+ const line = lines[currentLine];
772
+ if (line === undefined)
773
+ break;
774
+ const availableChars = line.length - currentChar;
775
+ if (availableChars <= 0) {
776
+ // Move to next line, add newline character
777
+ if (remaining > 0 && currentLine < lines.length - 1) {
778
+ result += '\n';
779
+ remaining--;
780
+ currentLine++;
781
+ currentChar = 0;
782
+ }
783
+ else {
784
+ break;
785
+ }
786
+ }
787
+ else {
788
+ const charsToTake = Math.min(availableChars, remaining);
789
+ result += line.substring(currentChar, currentChar + charsToTake);
790
+ remaining -= charsToTake;
791
+ currentChar += charsToTake;
792
+ // If we've consumed the line and need more, add newline and move to next
793
+ if (remaining > 0 && currentChar >= line.length && currentLine < lines.length - 1) {
794
+ result += '\n';
795
+ remaining--;
796
+ currentLine++;
797
+ currentChar = 0;
798
+ }
799
+ }
800
+ }
801
+ return result;
802
+ }
803
+ /**
804
+ * Check if a fix is idempotent (applying twice has no additional effect).
805
+ *
806
+ * A fix is idempotent if after applying it once, the content at the
807
+ * fix's range already matches the newText, so applying again has no effect.
808
+ *
809
+ * @param fix - The quick fix to check
810
+ * @param content - The original file content
811
+ * @returns True if the fix is idempotent
812
+ */
813
+ isIdempotent(fix, content) {
814
+ // Apply the fix once
815
+ const afterFirst = this.applyFix(fix, content);
816
+ // Check if the content at each edit range now matches the newText
817
+ // If so, applying again would have no effect
818
+ for (const [_file, edits] of Object.entries(fix.edit.changes)) {
819
+ for (const edit of edits) {
820
+ const currentText = this.extractTextFromRange(afterFirst, edit.range);
821
+ // If the range is now out of bounds or the text doesn't match newText,
822
+ // the fix might not be idempotent in the traditional sense
823
+ // But for our purposes, we check if applying twice gives same result
824
+ if (currentText !== edit.newText) {
825
+ // The range content changed, check if applying again changes anything
826
+ const afterSecond = this.applyFix(fix, afterFirst);
827
+ return afterFirst === afterSecond;
828
+ }
829
+ }
830
+ }
831
+ return true;
832
+ }
833
+ /**
834
+ * Validate a fix before applying.
835
+ *
836
+ * @param fix - The quick fix to validate
837
+ * @param content - The file content
838
+ * @returns Validation result with any errors
839
+ */
840
+ validateFix(fix, content) {
841
+ const errors = [];
842
+ // Check that all ranges are within bounds
843
+ const lines = content.split('\n');
844
+ for (const [_file, edits] of Object.entries(fix.edit.changes)) {
845
+ for (const edit of edits) {
846
+ if (edit.range.start.line < 0 || edit.range.start.line >= lines.length) {
847
+ errors.push(`Invalid start line: ${edit.range.start.line}`);
848
+ }
849
+ if (edit.range.end.line < 0 || edit.range.end.line >= lines.length) {
850
+ errors.push(`Invalid end line: ${edit.range.end.line}`);
851
+ }
852
+ if (edit.range.start.line > edit.range.end.line) {
853
+ errors.push('Start line is after end line');
854
+ }
855
+ if (edit.range.start.line === edit.range.end.line &&
856
+ edit.range.start.character > edit.range.end.character) {
857
+ errors.push('Start character is after end character');
858
+ }
859
+ }
860
+ }
861
+ return {
862
+ valid: errors.length === 0,
863
+ errors,
864
+ };
865
+ }
866
+ /**
867
+ * Get available fix types for a violation.
868
+ *
869
+ * @param violation - The violation to check
870
+ * @returns Array of fix types that can handle this violation
871
+ */
872
+ getAvailableFixTypes(violation) {
873
+ return this.strategies
874
+ .filter(strategy => strategy.canHandle(violation))
875
+ .map(strategy => strategy.type);
876
+ }
877
+ /**
878
+ * Register a custom fix strategy.
879
+ *
880
+ * @param strategy - The fix strategy to register
881
+ */
882
+ registerStrategy(strategy) {
883
+ // Remove existing strategy of same type
884
+ this.strategies = this.strategies.filter(s => s.type !== strategy.type);
885
+ this.strategies.push(strategy);
886
+ }
887
+ /**
888
+ * Get all registered fix strategies.
889
+ *
890
+ * @returns Array of registered strategies
891
+ */
892
+ getStrategies() {
893
+ return [...this.strategies];
894
+ }
895
+ /**
896
+ * Calculate the impact of a fix.
897
+ *
898
+ * @param fix - The quick fix to assess
899
+ * @param content - The file content
900
+ * @returns Impact assessment
901
+ */
902
+ calculateImpact(fix, content) {
903
+ let filesAffected = 0;
904
+ let linesChanged = 0;
905
+ for (const [_file, edits] of Object.entries(fix.edit.changes)) {
906
+ filesAffected++;
907
+ for (const edit of edits) {
908
+ const originalLines = edit.range.end.line - edit.range.start.line + 1;
909
+ const newLines = edit.newText.split('\n').length;
910
+ linesChanged += Math.max(originalLines, newLines);
911
+ }
912
+ }
913
+ // Determine risk level based on changes
914
+ let riskLevel = 'low';
915
+ if (linesChanged > 50 || filesAffected > 3) {
916
+ riskLevel = 'high';
917
+ }
918
+ else if (linesChanged > 10 || filesAffected > 1) {
919
+ riskLevel = 'medium';
920
+ }
921
+ // Check for breaking changes (simplified heuristic)
922
+ const breakingChange = this.mightBeBreaking(fix, content);
923
+ return {
924
+ filesAffected,
925
+ linesChanged,
926
+ riskLevel,
927
+ breakingChange,
928
+ };
929
+ }
930
+ // ============================================================================
931
+ // Private Methods
932
+ // ============================================================================
933
+ /**
934
+ * Add metadata to a quick fix.
935
+ */
936
+ addMetadata(fix, violation, fixType) {
937
+ this.fixIdCounter++;
938
+ return {
939
+ ...fix,
940
+ id: `fix-${Date.now()}-${this.fixIdCounter}`,
941
+ fixType,
942
+ violationId: violation.id,
943
+ patternId: violation.patternId,
944
+ validated: this.config.validateFixes ? this.validateFix(fix, '').valid : false,
945
+ };
946
+ }
947
+ /**
948
+ * Extract text from a range in content.
949
+ */
950
+ extractTextFromRange(content, range) {
951
+ const lines = content.split('\n');
952
+ const startLine = range.start.line;
953
+ const endLine = range.end.line;
954
+ if (startLine < 0 || endLine >= lines.length) {
955
+ return '';
956
+ }
957
+ if (startLine === endLine) {
958
+ const line = lines[startLine];
959
+ if (line === undefined)
960
+ return '';
961
+ return line.substring(range.start.character, range.end.character);
962
+ }
963
+ const extractedLines = [];
964
+ for (let i = startLine; i <= endLine; i++) {
965
+ const line = lines[i];
966
+ if (line === undefined)
967
+ continue;
968
+ if (i === startLine) {
969
+ extractedLines.push(line.substring(range.start.character));
970
+ }
971
+ else if (i === endLine) {
972
+ extractedLines.push(line.substring(0, range.end.character));
973
+ }
974
+ else {
975
+ extractedLines.push(line);
976
+ }
977
+ }
978
+ return extractedLines.join('\n');
979
+ }
980
+ /**
981
+ * Apply a text edit to content.
982
+ */
983
+ applyTextEdit(content, edit) {
984
+ const lines = content.split('\n');
985
+ const startLine = edit.range.start.line;
986
+ const endLine = edit.range.end.line;
987
+ if (startLine < 0 || startLine >= lines.length) {
988
+ return content;
989
+ }
990
+ // Handle single-line edit
991
+ if (startLine === endLine) {
992
+ const line = lines[startLine];
993
+ if (line === undefined)
994
+ return content;
995
+ const before = line.substring(0, edit.range.start.character);
996
+ const after = line.substring(edit.range.end.character);
997
+ lines[startLine] = before + edit.newText + after;
998
+ return lines.join('\n');
999
+ }
1000
+ // Handle multi-line edit
1001
+ const firstLine = lines[startLine];
1002
+ const lastLine = lines[endLine];
1003
+ if (firstLine === undefined || lastLine === undefined)
1004
+ return content;
1005
+ const before = firstLine.substring(0, edit.range.start.character);
1006
+ const after = lastLine.substring(edit.range.end.character);
1007
+ // Remove lines between start and end
1008
+ lines.splice(startLine, endLine - startLine + 1, before + edit.newText + after);
1009
+ return lines.join('\n');
1010
+ }
1011
+ /**
1012
+ * Check if a fix might be breaking.
1013
+ */
1014
+ mightBeBreaking(fix, _content) {
1015
+ // Check for deletions of significant code
1016
+ for (const [_file, edits] of Object.entries(fix.edit.changes)) {
1017
+ for (const edit of edits) {
1018
+ // Deletion of multiple lines might be breaking
1019
+ if (edit.newText === '' && edit.range.end.line - edit.range.start.line > 5) {
1020
+ return true;
1021
+ }
1022
+ // Renaming exports might be breaking
1023
+ if (edit.newText.includes('export') || edit.newText.includes('module.exports')) {
1024
+ return true;
1025
+ }
1026
+ }
1027
+ }
1028
+ return false;
1029
+ }
1030
+ }
1031
+ // ============================================================================
1032
+ // Factory Functions
1033
+ // ============================================================================
1034
+ /**
1035
+ * Create a QuickFixGenerator with default configuration.
1036
+ *
1037
+ * @returns New QuickFixGenerator instance
1038
+ */
1039
+ export function createQuickFixGenerator() {
1040
+ return new QuickFixGenerator();
1041
+ }
1042
+ /**
1043
+ * Create a QuickFixGenerator with custom configuration.
1044
+ *
1045
+ * @param config - Configuration options
1046
+ * @returns New QuickFixGenerator instance
1047
+ */
1048
+ export function createQuickFixGeneratorWithConfig(config) {
1049
+ return new QuickFixGenerator(config);
1050
+ }
1051
+ /**
1052
+ * Create a QuickFixGenerator with high confidence threshold.
1053
+ *
1054
+ * @returns New QuickFixGenerator instance with high confidence threshold
1055
+ */
1056
+ export function createHighConfidenceQuickFixGenerator() {
1057
+ return new QuickFixGenerator({
1058
+ minConfidence: 0.8,
1059
+ maxFixesPerViolation: 3,
1060
+ });
1061
+ }
1062
+ /**
1063
+ * Create a QuickFixGenerator with all fixes enabled.
1064
+ *
1065
+ * @returns New QuickFixGenerator instance with all fixes
1066
+ */
1067
+ export function createFullQuickFixGenerator() {
1068
+ return new QuickFixGenerator({
1069
+ minConfidence: 0.0,
1070
+ maxFixesPerViolation: 10,
1071
+ generatePreviews: true,
1072
+ validateFixes: true,
1073
+ });
1074
+ }
1075
+ //# sourceMappingURL=quick-fix-generator.js.map