codescoop 1.0.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.
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Ghost Classes Detector
3
+ * Finds classes in HTML that have no matching CSS rules anywhere in the project
4
+ *
5
+ * UPDATES:
6
+ * - Enhanced documentation
7
+ * - Better handling of modern CSS frameworks
8
+ */
9
+
10
+ /**
11
+ * Detect ghost classes (classes with no CSS definitions)
12
+ * @param {Object} targetInfo - Target element info with classes
13
+ * @param {Array} cssResults - CSS analysis results
14
+ * @param {Array} cssLibraryResults - Library CSS results
15
+ * @param {Array} inlineStyles - Inline style results
16
+ * @returns {Object} Ghost class detection results
17
+ */
18
+ function detectGhostClasses(targetInfo, cssResults = [], cssLibraryResults = [], inlineStyles = []) {
19
+ const { classes = [] } = targetInfo;
20
+
21
+ if (classes.length === 0) {
22
+ return {
23
+ ghostClasses: [],
24
+ definedClasses: [],
25
+ totalClasses: 0
26
+ };
27
+ }
28
+
29
+ // Collect all classes that have CSS definitions
30
+ const definedClasses = new Set();
31
+
32
+ // Helper function to process matchedOn (can be string or array)
33
+ const processMatchedOn = (matchedOn) => {
34
+ const items = Array.isArray(matchedOn) ? matchedOn : [matchedOn];
35
+ for (const item of items) {
36
+ if (typeof item === 'string' && item.startsWith('class:')) {
37
+ definedClasses.add('.' + item.replace('class: ', '').replace('class:', '').trim());
38
+ }
39
+ }
40
+ };
41
+
42
+ // Check custom CSS results
43
+ for (const result of cssResults) {
44
+ for (const match of result.matches || []) {
45
+ // Extract class name from match info
46
+ processMatchedOn(match.matchedOn);
47
+
48
+ // Also check the selector directly (handles :is(), :where(), etc.)
49
+ const selector = match.selector || '';
50
+ for (const cls of classes) {
51
+ if (selector.includes(cls)) {
52
+ definedClasses.add(cls);
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ // Check library CSS results
59
+ for (const result of cssLibraryResults) {
60
+ for (const match of result.matches || []) {
61
+ processMatchedOn(match.matchedOn);
62
+
63
+ const selector = match.selector || '';
64
+ for (const cls of classes) {
65
+ if (selector.includes(cls)) {
66
+ definedClasses.add(cls);
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ // Check inline styles (less common to match classes, but check anyway)
73
+ for (const style of inlineStyles) {
74
+ const content = style.content || '';
75
+ for (const cls of classes) {
76
+ if (content.includes(cls)) {
77
+ definedClasses.add(cls);
78
+ }
79
+ }
80
+ }
81
+
82
+ // Common utility patterns to ignore (Tailwind, Bootstrap, State classes)
83
+ const IGNORED_PATTERNS = [
84
+ // Tailwind / Utility patterns
85
+ /^(p|m)[xytrbl]?-\d+/, // Spacing: p-4, my-2, mt-10
86
+ /^text-(xs|sm|base|lg|xl|\d+xl)/, // Typo sizes
87
+ /^text-(black|white|gray|red|blue|green|yellow|indigo|purple|pink)/, // Colors
88
+ /^bg-(black|white|gray|red|blue|green|yellow|indigo|purple|pink)/, // Backgrounds
89
+ /^font-(sans|serif|mono|bold|medium|light)/, // Fonts
90
+ /^flex/, /^grid/, /^block/, /^hidden/, /^inline/, // Layout
91
+ /^w-\d+/, /^h-\d+/, /^w-full/, /^h-full/, /^min-w/, /^max-w/, // Sizing
92
+ /^border/, /^rounded/, /^shadow/, // Decorations
93
+ /^items-/, /^justify-/, /^place-/, /^gap-/, // Flex/Grid alignment
94
+ /^absolute/, /^relative/, /^fixed/, /^sticky/, // Position
95
+ /^top-/, /^bottom-/, /^left-/, /^right-/, /^z-/, // Positioning
96
+ /^opacity-/, /^cursor-/, /^pointer-events-/, // Interaction
97
+ /^transition/, /^duration-/, /^ease-/, // Transitions
98
+ /^hover:/, /^focus:/, /^active:/, /^group-hover:/, // State modifiers
99
+ /^sm:/, /^md:/, /^lg:/, /^xl:/, /^2xl:/, // Responsive modifiers
100
+
101
+ // Bootstrap patterns
102
+ /^col-/, /^row/, /^container/, /^btn-/, /^alert-/, /^card-/,
103
+ /^navbar-/, /^nav-/, /^dropdown-/, /^modal-/, /^tab-/, /^form-/,
104
+
105
+ // Icons
106
+ /^fa-/, /^icon-/, /^material-/, /^bi-/, /^ri-/, /^bx-/,
107
+
108
+ // State hooks & JS hooks (often not defined in CSS directly but added by JS)
109
+ /^js-/, /^is-/, /^has-/, /^active/, /^open/, /^show/, /^visible/,
110
+
111
+ // Animation libraries
112
+ /^animate__/, /^aos-/, /^fade/, /^slide/, /^zoom/,
113
+
114
+ // Utility libraries
115
+ /^u-/, /^util-/, /^helper-/
116
+ ];
117
+
118
+ // Find ghost classes (in HTML but not in any CSS)
119
+ const ghostClasses = classes.filter(cls => {
120
+ const className = cls.startsWith('.') ? cls : '.' + cls;
121
+ const cleanName = cls.startsWith('.') ? cls.substring(1) : cls;
122
+
123
+ // Check if it exists in CSS
124
+ if (definedClasses.has(className) || definedClasses.has(cls)) {
125
+ return false;
126
+ }
127
+
128
+ // Check against allowlist (ignore utilities)
129
+ const isUtility = IGNORED_PATTERNS.some(pattern => pattern.test(cleanName));
130
+ if (isUtility) {
131
+ return false;
132
+ }
133
+
134
+ return true;
135
+ });
136
+
137
+ return {
138
+ ghostClasses,
139
+ definedClasses: Array.from(definedClasses),
140
+ totalClasses: classes.length,
141
+ hasGhosts: ghostClasses.length > 0
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Format ghost classes as markdown section
147
+ * @param {Object} ghostData - Ghost detection results
148
+ * @returns {string} Markdown section
149
+ */
150
+ function formatGhostClassesMarkdown(ghostData) {
151
+ if (!ghostData.hasGhosts) {
152
+ return '';
153
+ }
154
+
155
+ let md = `\n---\n\n## 👻 Ghost Classes Detected\n\n`;
156
+ md += `> These classes are used in the HTML but have **no matching CSS rules** in the project.\n`;
157
+ md += `> This may indicate dead code, missing stylesheets, or dynamically applied styles.\n\n`;
158
+
159
+ md += `| Class | Status |\n`;
160
+ md += `|-------|--------|\n`;
161
+
162
+ for (const ghost of ghostData.ghostClasses) {
163
+ md += `| \`${ghost}\` | ⚠️ No CSS found |\n`;
164
+ }
165
+
166
+ md += `\n**${ghostData.ghostClasses.length}** ghost class(es) out of **${ghostData.totalClasses}** total.\n`;
167
+
168
+ return md;
169
+ }
170
+
171
+ module.exports = {
172
+ detectGhostClasses,
173
+ formatGhostClassesMarkdown
174
+ };
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Library Detector Module
3
+ * Identifies common frontend libraries and frameworks
4
+ */
5
+
6
+ const path = require('path');
7
+
8
+ /**
9
+ * Known libraries and their detection patterns
10
+ */
11
+ const KNOWN_LIBRARIES = {
12
+ // CSS Frameworks
13
+ 'Bootstrap': {
14
+ type: 'css-framework',
15
+ patterns: [/bootstrap/i],
16
+ cdnPatterns: [/cdn.*bootstrap/i, /bootstrapcdn/i],
17
+ classPatterns: [/^btn-/, /^col-/, /^row$/, /^container/, /^navbar-/, /^modal-/, /^carousel-/],
18
+ website: 'https://getbootstrap.com'
19
+ },
20
+ 'Tailwind CSS': {
21
+ type: 'css-framework',
22
+ patterns: [/tailwind/i],
23
+ classPatterns: [/^(flex|grid|p-|m-|text-|bg-|w-|h-)/],
24
+ website: 'https://tailwindcss.com'
25
+ },
26
+ 'Foundation': {
27
+ type: 'css-framework',
28
+ patterns: [/foundation/i],
29
+ website: 'https://get.foundation'
30
+ },
31
+
32
+ // JS Libraries
33
+ 'jQuery': {
34
+ type: 'js-library',
35
+ patterns: [/jquery/i],
36
+ cdnPatterns: [/code\.jquery\.com/i, /cdn.*jquery/i],
37
+ codePatterns: [/\$\s*\(/, /jQuery\s*\(/],
38
+ website: 'https://jquery.com'
39
+ },
40
+ 'GSAP': {
41
+ type: 'js-library',
42
+ patterns: [/gsap/i, /greensock/i],
43
+ codePatterns: [/gsap\./i, /TweenMax/i, /TweenLite/i, /TimelineMax/i],
44
+ website: 'https://greensock.com/gsap'
45
+ },
46
+ 'ScrollMagic': {
47
+ type: 'js-library',
48
+ patterns: [/scrollmagic/i],
49
+ codePatterns: [/ScrollMagic/i],
50
+ website: 'https://scrollmagic.io'
51
+ },
52
+ 'Swiper': {
53
+ type: 'js-library',
54
+ patterns: [/swiper/i],
55
+ classPatterns: [/^swiper-/],
56
+ codePatterns: [/new Swiper/i],
57
+ website: 'https://swiperjs.com'
58
+ },
59
+ 'Isotope': {
60
+ type: 'js-library',
61
+ patterns: [/isotope/i, /isotop/i],
62
+ codePatterns: [/\.isotope\(/i],
63
+ website: 'https://isotope.metafizzy.co'
64
+ },
65
+ 'Masonry': {
66
+ type: 'js-library',
67
+ patterns: [/masonry/i],
68
+ codePatterns: [/\.masonry\(/i, /new Masonry/i],
69
+ website: 'https://masonry.desandro.com'
70
+ },
71
+ 'AOS': {
72
+ type: 'js-library',
73
+ patterns: [/aos\.js/i, /aos\.css/i],
74
+ classPatterns: [/^aos-/],
75
+ codePatterns: [/AOS\.init/i],
76
+ website: 'https://michalsnik.github.io/aos'
77
+ },
78
+ 'Animate.css': {
79
+ type: 'css-library',
80
+ patterns: [/animate\.css/i, /animate\.min\.css/i],
81
+ classPatterns: [/^animate__/, /^animated$/],
82
+ website: 'https://animate.style'
83
+ },
84
+ 'Locomotive Scroll': {
85
+ type: 'js-library',
86
+ patterns: [/locomotive/i],
87
+ codePatterns: [/LocomotiveScroll/i],
88
+ website: 'https://locomotivemtl.github.io/locomotive-scroll'
89
+ },
90
+
91
+ // Icon Libraries
92
+ 'Font Awesome': {
93
+ type: 'icon-library',
94
+ patterns: [/fontawesome/i, /font-awesome/i],
95
+ classPatterns: [/^fa-/, /^fas$/, /^fab$/, /^far$/, /^fal$/],
96
+ website: 'https://fontawesome.com'
97
+ },
98
+ 'Material Icons': {
99
+ type: 'icon-library',
100
+ patterns: [/material-icons/i, /material-design-icons/i],
101
+ classPatterns: [/^material-icons/],
102
+ website: 'https://fonts.google.com/icons'
103
+ },
104
+ 'Boxicons': {
105
+ type: 'icon-library',
106
+ patterns: [/boxicons/i],
107
+ classPatterns: [/^bx-/, /^bx$/],
108
+ website: 'https://boxicons.com'
109
+ },
110
+ 'Remix Icon': {
111
+ type: 'icon-library',
112
+ patterns: [/remixicon/i],
113
+ classPatterns: [/^ri-/],
114
+ website: 'https://remixicon.com'
115
+ },
116
+
117
+ // Utility Libraries
118
+ 'Lodash': {
119
+ type: 'js-library',
120
+ patterns: [/lodash/i],
121
+ codePatterns: [/_\.\w+\(/],
122
+ website: 'https://lodash.com'
123
+ },
124
+ 'Axios': {
125
+ type: 'js-library',
126
+ patterns: [/axios/i],
127
+ codePatterns: [/axios\.(get|post|put|delete)/i],
128
+ website: 'https://axios-http.com'
129
+ },
130
+
131
+ // Image/Media
132
+ 'Lightbox': {
133
+ type: 'js-library',
134
+ patterns: [/lightbox/i],
135
+ codePatterns: [/lightbox/i],
136
+ website: 'https://lokeshdhakar.com/projects/lightbox2'
137
+ },
138
+ 'Fancybox': {
139
+ type: 'js-library',
140
+ patterns: [/fancybox/i],
141
+ codePatterns: [/fancybox/i],
142
+ website: 'https://fancyapps.com/fancybox'
143
+ },
144
+ 'ImagesLoaded': {
145
+ type: 'js-library',
146
+ patterns: [/imagesloaded/i],
147
+ codePatterns: [/imagesLoaded/i],
148
+ website: 'https://imagesloaded.desandro.com'
149
+ },
150
+
151
+ // Form Libraries
152
+ 'Select2': {
153
+ type: 'js-library',
154
+ patterns: [/select2/i],
155
+ classPatterns: [/^select2-/],
156
+ codePatterns: [/\.select2\(/i],
157
+ website: 'https://select2.org'
158
+ },
159
+
160
+ // Charts
161
+ 'Chart.js': {
162
+ type: 'js-library',
163
+ patterns: [/chart\.js/i, /chartjs/i],
164
+ codePatterns: [/new Chart/i],
165
+ website: 'https://www.chartjs.org'
166
+ },
167
+
168
+ // Scroll Libraries
169
+ 'Smooth Scroll': {
170
+ type: 'js-library',
171
+ patterns: [/smooth-scroll/i, /smoothscroll/i],
172
+ codePatterns: [/SmoothScroll/i],
173
+ website: ''
174
+ },
175
+ 'ScrollTrigger': {
176
+ type: 'js-library',
177
+ patterns: [/scrolltrigger/i, /scrolltiger/i],
178
+ codePatterns: [/ScrollTrigger/i],
179
+ website: 'https://greensock.com/scrolltrigger'
180
+ }
181
+ };
182
+
183
+ /**
184
+ * Detect libraries from file paths
185
+ * @param {string[]} filePaths - Array of file paths
186
+ * @returns {Object} Detected libraries with their import status
187
+ */
188
+ function detectLibrariesFromPaths(filePaths) {
189
+ const detected = {};
190
+
191
+ for (const filePath of filePaths) {
192
+ const fileName = path.basename(filePath).toLowerCase();
193
+ const fullPath = filePath.toLowerCase();
194
+
195
+ for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
196
+ // Check file path patterns
197
+ const matchesPattern = libInfo.patterns?.some(pattern =>
198
+ pattern.test(fileName) || pattern.test(fullPath)
199
+ );
200
+
201
+ if (matchesPattern) {
202
+ if (!detected[libName]) {
203
+ detected[libName] = {
204
+ ...libInfo,
205
+ name: libName,
206
+ files: [],
207
+ importedFiles: [],
208
+ missingFiles: []
209
+ };
210
+ }
211
+ detected[libName].files.push(filePath);
212
+ }
213
+ }
214
+ }
215
+
216
+ return detected;
217
+ }
218
+
219
+ /**
220
+ * Detect libraries from HTML content (CDN links)
221
+ * @param {CheerioAPI} $ - Cheerio instance
222
+ * @returns {Object} Libraries loaded via CDN
223
+ */
224
+ function detectLibrariesFromHTML($) {
225
+ const cdnLibraries = [];
226
+
227
+ // Check link tags
228
+ $('link[href]').each((_, el) => {
229
+ const href = $(el).attr('href') || '';
230
+ for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
231
+ const matchesCDN = libInfo.cdnPatterns?.some(pattern => pattern.test(href));
232
+ const matchesPattern = libInfo.patterns?.some(pattern => pattern.test(href));
233
+
234
+ if (matchesCDN || (matchesPattern && href.includes('cdn'))) {
235
+ cdnLibraries.push({
236
+ name: libName,
237
+ type: libInfo.type,
238
+ source: 'cdn',
239
+ url: href,
240
+ website: libInfo.website
241
+ });
242
+ }
243
+ }
244
+ });
245
+
246
+ // Check script tags
247
+ $('script[src]').each((_, el) => {
248
+ const src = $(el).attr('src') || '';
249
+ for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
250
+ const matchesCDN = libInfo.cdnPatterns?.some(pattern => pattern.test(src));
251
+ const matchesPattern = libInfo.patterns?.some(pattern => pattern.test(src));
252
+
253
+ if (matchesCDN || (matchesPattern && src.includes('cdn'))) {
254
+ cdnLibraries.push({
255
+ name: libName,
256
+ type: libInfo.type,
257
+ source: 'cdn',
258
+ url: src,
259
+ website: libInfo.website
260
+ });
261
+ }
262
+ }
263
+ });
264
+
265
+ return cdnLibraries;
266
+ }
267
+
268
+ /**
269
+ * Detect libraries from class names in HTML
270
+ * @param {string[]} classes - Array of class names
271
+ * @returns {string[]} Library names detected from classes
272
+ */
273
+ function detectLibrariesFromClasses(classes) {
274
+ const detected = new Set();
275
+
276
+ for (const className of classes) {
277
+ for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
278
+ if (libInfo.classPatterns) {
279
+ const matches = libInfo.classPatterns.some(pattern => pattern.test(className));
280
+ if (matches) {
281
+ detected.add(libName);
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ return Array.from(detected);
288
+ }
289
+
290
+ /**
291
+ * Check if a file is a library file
292
+ * @param {string} filePath - Path to the file
293
+ * @returns {Object|null} Library info if it's a library file
294
+ */
295
+ function isLibraryFile(filePath) {
296
+ const fileName = path.basename(filePath).toLowerCase();
297
+ const fullPath = filePath.toLowerCase();
298
+
299
+ for (const [libName, libInfo] of Object.entries(KNOWN_LIBRARIES)) {
300
+ const matchesPattern = libInfo.patterns?.some(pattern =>
301
+ pattern.test(fileName) || pattern.test(fullPath)
302
+ );
303
+
304
+ if (matchesPattern) {
305
+ return { name: libName, ...libInfo };
306
+ }
307
+ }
308
+
309
+ // Also check for common library indicators
310
+ if (fileName.includes('.min.') &&
311
+ (fileName.includes('plugin') ||
312
+ fullPath.includes('/plugins/') ||
313
+ fullPath.includes('/vendor/') ||
314
+ fullPath.includes('/lib/'))) {
315
+ return { name: 'Unknown Plugin', type: 'plugin' };
316
+ }
317
+
318
+ return null;
319
+ }
320
+
321
+ /**
322
+ * Get all known library names
323
+ */
324
+ function getKnownLibraryNames() {
325
+ return Object.keys(KNOWN_LIBRARIES);
326
+ }
327
+
328
+ module.exports = {
329
+ KNOWN_LIBRARIES,
330
+ detectLibrariesFromPaths,
331
+ detectLibrariesFromHTML,
332
+ detectLibrariesFromClasses,
333
+ isLibraryFile,
334
+ getKnownLibraryNames
335
+ };