baseguard 1.0.2 → 1.0.4

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 (169) hide show
  1. package/.baseguardrc.example.json +63 -63
  2. package/.eslintrc.json +24 -24
  3. package/.prettierrc +7 -7
  4. package/CHANGELOG.md +195 -195
  5. package/DEPLOYMENT.md +624 -624
  6. package/DEPLOYMENT_CHECKLIST.md +239 -239
  7. package/DEPLOYMENT_SUMMARY_v1.0.2.md +202 -202
  8. package/QUICK_START.md +134 -134
  9. package/README.md +488 -488
  10. package/RELEASE_NOTES_v1.0.2.md +434 -434
  11. package/bin/base.js +628 -613
  12. package/dist/ai/fix-manager.d.ts.map +1 -1
  13. package/dist/ai/fix-manager.js +1 -1
  14. package/dist/ai/fix-manager.js.map +1 -1
  15. package/dist/ai/gemini-analyzer.d.ts.map +1 -1
  16. package/dist/ai/gemini-analyzer.js +29 -35
  17. package/dist/ai/gemini-analyzer.js.map +1 -1
  18. package/dist/ai/gemini-code-fixer.d.ts.map +1 -1
  19. package/dist/ai/gemini-code-fixer.js +58 -58
  20. package/dist/ai/gemini-code-fixer.js.map +1 -1
  21. package/dist/ai/jules-implementer.d.ts +3 -0
  22. package/dist/ai/jules-implementer.d.ts.map +1 -1
  23. package/dist/ai/jules-implementer.js +63 -32
  24. package/dist/ai/jules-implementer.js.map +1 -1
  25. package/dist/ai/unified-code-fixer.js.map +1 -1
  26. package/dist/commands/check.d.ts.map +1 -1
  27. package/dist/commands/check.js +1 -1
  28. package/dist/commands/check.js.map +1 -1
  29. package/dist/commands/config.js +2 -1
  30. package/dist/commands/config.js.map +1 -1
  31. package/dist/commands/fix.d.ts.map +1 -1
  32. package/dist/commands/fix.js +44 -15
  33. package/dist/commands/fix.js.map +1 -1
  34. package/dist/core/api-key-manager.js +2 -2
  35. package/dist/core/api-key-manager.js.map +1 -1
  36. package/dist/core/baseguard.d.ts +1 -0
  37. package/dist/core/baseguard.d.ts.map +1 -1
  38. package/dist/core/baseguard.js +13 -10
  39. package/dist/core/baseguard.js.map +1 -1
  40. package/dist/core/baseline-checker.d.ts.map +1 -1
  41. package/dist/core/baseline-checker.js +2 -1
  42. package/dist/core/baseline-checker.js.map +1 -1
  43. package/dist/core/configuration-recovery.d.ts.map +1 -1
  44. package/dist/core/configuration-recovery.js +1 -1
  45. package/dist/core/configuration-recovery.js.map +1 -1
  46. package/dist/core/debug-logger.d.ts.map +1 -1
  47. package/dist/core/debug-logger.js +1 -1
  48. package/dist/core/debug-logger.js.map +1 -1
  49. package/dist/core/error-handler.d.ts.map +1 -1
  50. package/dist/core/error-handler.js +2 -1
  51. package/dist/core/error-handler.js.map +1 -1
  52. package/dist/core/gitignore-manager.js +5 -5
  53. package/dist/core/graceful-degradation-manager.d.ts.map +1 -1
  54. package/dist/core/graceful-degradation-manager.js +16 -16
  55. package/dist/core/graceful-degradation-manager.js.map +1 -1
  56. package/dist/core/lazy-loader.d.ts.map +1 -1
  57. package/dist/core/lazy-loader.js +9 -2
  58. package/dist/core/lazy-loader.js.map +1 -1
  59. package/dist/core/memory-manager.d.ts +0 -3
  60. package/dist/core/memory-manager.d.ts.map +1 -1
  61. package/dist/core/memory-manager.js.map +1 -1
  62. package/dist/core/parser-worker.d.ts +2 -0
  63. package/dist/core/parser-worker.d.ts.map +1 -0
  64. package/dist/core/parser-worker.js +19 -0
  65. package/dist/core/parser-worker.js.map +1 -0
  66. package/dist/core/startup-optimizer.d.ts +2 -0
  67. package/dist/core/startup-optimizer.d.ts.map +1 -1
  68. package/dist/core/startup-optimizer.js +19 -12
  69. package/dist/core/startup-optimizer.js.map +1 -1
  70. package/dist/core/system-error-handler.d.ts.map +1 -1
  71. package/dist/core/system-error-handler.js +18 -11
  72. package/dist/core/system-error-handler.js.map +1 -1
  73. package/dist/git/automation-engine.d.ts.map +1 -1
  74. package/dist/git/automation-engine.js +5 -4
  75. package/dist/git/automation-engine.js.map +1 -1
  76. package/dist/git/github-manager.d.ts.map +1 -1
  77. package/dist/git/github-manager.js.map +1 -1
  78. package/dist/git/hook-manager.js +5 -5
  79. package/dist/git/hook-manager.js.map +1 -1
  80. package/dist/parsers/parser-manager.d.ts.map +1 -1
  81. package/dist/parsers/parser-manager.js +1 -1
  82. package/dist/parsers/parser-manager.js.map +1 -1
  83. package/dist/parsers/svelte-parser.js +1 -1
  84. package/dist/parsers/svelte-parser.js.map +1 -1
  85. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  86. package/dist/parsers/vanilla-parser.js.map +1 -1
  87. package/dist/parsers/vue-parser.d.ts.map +1 -1
  88. package/dist/parsers/vue-parser.js.map +1 -1
  89. package/dist/ui/components.d.ts +1 -1
  90. package/dist/ui/components.d.ts.map +1 -1
  91. package/dist/ui/components.js +11 -11
  92. package/dist/ui/components.js.map +1 -1
  93. package/dist/ui/terminal-header.js +14 -14
  94. package/package.json +105 -105
  95. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  96. package/src/ai/agentkit-orchestrator.ts +533 -533
  97. package/src/ai/fix-manager.ts +362 -362
  98. package/src/ai/gemini-analyzer.ts +665 -671
  99. package/src/ai/gemini-code-fixer.ts +539 -540
  100. package/src/ai/index.ts +3 -3
  101. package/src/ai/jules-implementer.ts +504 -460
  102. package/src/ai/unified-code-fixer.ts +347 -347
  103. package/src/commands/automation.ts +343 -343
  104. package/src/commands/check.ts +298 -299
  105. package/src/commands/config.ts +584 -583
  106. package/src/commands/fix.ts +264 -238
  107. package/src/commands/index.ts +6 -6
  108. package/src/commands/init.ts +155 -155
  109. package/src/commands/status.ts +306 -306
  110. package/src/core/api-key-manager.ts +298 -298
  111. package/src/core/baseguard.ts +757 -756
  112. package/src/core/baseline-checker.ts +564 -563
  113. package/src/core/cache-manager.ts +271 -271
  114. package/src/core/configuration-recovery.ts +672 -673
  115. package/src/core/configuration.ts +595 -595
  116. package/src/core/debug-logger.ts +590 -590
  117. package/src/core/directory-filter.ts +420 -420
  118. package/src/core/error-handler.ts +518 -517
  119. package/src/core/file-processor.ts +337 -337
  120. package/src/core/gitignore-manager.ts +168 -168
  121. package/src/core/graceful-degradation-manager.ts +596 -596
  122. package/src/core/index.ts +16 -16
  123. package/src/core/lazy-loader.ts +317 -307
  124. package/src/core/memory-manager.ts +290 -295
  125. package/src/core/parser-worker.ts +33 -0
  126. package/src/core/startup-optimizer.ts +246 -243
  127. package/src/core/system-error-handler.ts +755 -750
  128. package/src/git/automation-engine.ts +361 -361
  129. package/src/git/github-manager.ts +190 -192
  130. package/src/git/hook-manager.ts +210 -210
  131. package/src/git/index.ts +3 -3
  132. package/src/index.ts +7 -7
  133. package/src/parsers/feature-validator.ts +558 -558
  134. package/src/parsers/index.ts +7 -7
  135. package/src/parsers/parser-manager.ts +418 -419
  136. package/src/parsers/parser.ts +25 -25
  137. package/src/parsers/react-parser-optimized.ts +160 -160
  138. package/src/parsers/react-parser.ts +358 -358
  139. package/src/parsers/svelte-parser.ts +510 -510
  140. package/src/parsers/vanilla-parser.ts +685 -686
  141. package/src/parsers/vue-parser.ts +476 -478
  142. package/src/types/index.ts +95 -95
  143. package/src/ui/components.ts +567 -567
  144. package/src/ui/help.ts +192 -192
  145. package/src/ui/index.ts +3 -3
  146. package/src/ui/prompts.ts +680 -680
  147. package/src/ui/terminal-header.ts +58 -58
  148. package/test-build.js +40 -40
  149. package/test-config-commands.js +55 -55
  150. package/test-header-simple.js +32 -32
  151. package/test-terminal-header.js +11 -11
  152. package/test-ui.js +28 -28
  153. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  154. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  155. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  156. package/tests/fixtures/react-project/package.json +13 -13
  157. package/tests/fixtures/react-project/src/App.css +75 -75
  158. package/tests/fixtures/react-project/src/App.tsx +76 -76
  159. package/tests/fixtures/svelte-project/package.json +10 -10
  160. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  161. package/tests/fixtures/vanilla-project/index.html +75 -75
  162. package/tests/fixtures/vanilla-project/script.js +330 -330
  163. package/tests/fixtures/vanilla-project/styles.css +358 -358
  164. package/tests/fixtures/vue-project/package.json +11 -11
  165. package/tests/fixtures/vue-project/src/App.vue +215 -215
  166. package/tsconfig.json +34 -34
  167. package/vitest.config.ts +11 -11
  168. package/dist/terminal-header.d.ts +0 -12
  169. package/dist/terminal-header.js +0 -45
@@ -1,686 +1,685 @@
1
- import { Parser } from './parser.js';
2
- import type { DetectedFeature } from '../types/index.js';
3
- import { LazyLoader } from '../core/lazy-loader.js';
4
- import { parse as parseBabel } from '@babel/parser';
5
- import traverse from '@babel/traverse';
6
- import * as t from '@babel/types';
7
- import postcss from 'postcss';
8
-
9
- /**
10
- * Vanilla JavaScript/CSS/HTML parser - extracts ALL web platform features
11
- * Handles .js, .ts, .html, and .css files with comprehensive feature detection
12
- */
13
- export class VanillaParser extends Parser {
14
- private readonly WEB_PLATFORM_APIS = new Set([
15
- // Canvas APIs
16
- 'getContext', 'CanvasRenderingContext2D', 'WebGLRenderingContext', 'WebGL2RenderingContext',
17
- 'OffscreenCanvas', 'ImageBitmap', 'createImageBitmap', 'Path2D', 'ImageData',
18
- 'CanvasGradient', 'CanvasPattern', 'TextMetrics',
19
-
20
- // WebGL specific
21
- 'createShader', 'shaderSource', 'compileShader', 'createProgram', 'attachShader',
22
- 'linkProgram', 'useProgram', 'getAttribLocation', 'getUniformLocation',
23
- 'enableVertexAttribArray', 'vertexAttribPointer', 'uniform1f', 'uniform2f',
24
- 'uniform3f', 'uniform4f', 'uniformMatrix4fv', 'drawArrays', 'drawElements',
25
-
26
- // WebRTC APIs
27
- 'RTCPeerConnection', 'RTCDataChannel', 'RTCSessionDescription', 'RTCIceCandidate',
28
- 'getUserMedia', 'getDisplayMedia', 'MediaStream', 'MediaStreamTrack',
29
- 'RTCRtpSender', 'RTCRtpReceiver', 'RTCStatsReport', 'RTCIceServer',
30
-
31
- // WebAssembly
32
- 'WebAssembly', 'instantiate', 'compile', 'validate', 'Module', 'Instance',
33
- 'Memory', 'Table', 'Global', 'CompileError', 'LinkError', 'RuntimeError',
34
-
35
- // Service Workers & PWA
36
- 'ServiceWorker', 'serviceWorker', 'register', 'unregister', 'update',
37
- 'Cache', 'caches', 'open', 'match', 'matchAll', 'add', 'addAll', 'put', 'delete',
38
- 'PushManager', 'subscribe', 'getSubscription', 'permissionState',
39
- 'Notification', 'showNotification', 'getNotifications', 'requestPermission',
40
- 'BackgroundSync', 'sync', 'getTags', 'BackgroundFetch',
41
-
42
- // DOM APIs
43
- 'querySelector', 'querySelectorAll', 'getElementById', 'getElementsByClassName',
44
- 'getElementsByTagName', 'createElement', 'createTextNode', 'createDocumentFragment',
45
- 'addEventListener', 'removeEventListener', 'dispatchEvent', 'CustomEvent',
46
- 'MutationObserver', 'observe', 'disconnect', 'takeRecords',
47
- 'ResizeObserver', 'IntersectionObserver', 'PerformanceObserver',
48
- 'AbortController', 'AbortSignal', 'abort', 'signal',
49
- 'FormData', 'append', 'set', 'get', 'getAll', 'has', 'entries', 'keys', 'values',
50
- 'URLSearchParams', 'URL', 'createObjectURL', 'revokeObjectURL',
51
-
52
- // Fetch API
53
- 'fetch', 'Request', 'Response', 'Headers', 'clone', 'json', 'text', 'blob',
54
- 'arrayBuffer', 'formData', 'ok', 'status', 'statusText', 'redirected',
55
-
56
- // File APIs
57
- 'Blob', 'File', 'FileReader', 'FileList', 'readAsText', 'readAsDataURL',
58
- 'readAsArrayBuffer', 'readAsBinaryString', 'slice',
59
-
60
- // Web APIs
61
- 'navigator', 'geolocation', 'getCurrentPosition', 'watchPosition', 'clearWatch',
62
- 'permissions', 'query', 'clipboard', 'writeText', 'readText', 'write', 'read',
63
- 'share', 'canShare', 'requestAnimationFrame', 'cancelAnimationFrame',
64
- 'requestIdleCallback', 'cancelIdleCallback', 'setTimeout', 'setInterval',
65
- 'clearTimeout', 'clearInterval', 'queueMicrotask',
66
-
67
- // Storage APIs
68
- 'localStorage', 'sessionStorage', 'getItem', 'setItem', 'removeItem', 'clear',
69
- 'indexedDB', 'open', 'deleteDatabase', 'cmp', 'IDBDatabase', 'IDBTransaction',
70
- 'IDBObjectStore', 'IDBIndex', 'IDBCursor', 'IDBKeyRange',
71
-
72
- // Crypto APIs
73
- 'crypto', 'getRandomValues', 'randomUUID', 'subtle', 'encrypt', 'decrypt',
74
- 'sign', 'verify', 'digest', 'generateKey', 'importKey', 'exportKey',
75
-
76
- // Performance APIs
77
- 'performance', 'now', 'mark', 'measure', 'getEntries', 'getEntriesByName',
78
- 'getEntriesByType', 'clearMarks', 'clearMeasures', 'timing', 'navigation',
79
-
80
- // Audio/Video APIs
81
- 'AudioContext', 'createOscillator', 'createGain', 'createAnalyser',
82
- 'createBiquadFilter', 'createBufferSource', 'createMediaElementSource',
83
- 'createScriptProcessor', 'createDynamicsCompressor', 'createConvolver',
84
- 'MediaRecorder', 'start', 'stop', 'pause', 'resume', 'requestData',
85
- 'MediaSource', 'SourceBuffer', 'appendBuffer', 'remove', 'abort',
86
- 'HTMLMediaElement', 'HTMLAudioElement', 'HTMLVideoElement', 'play', 'pause',
87
- 'load', 'canPlayType', 'addTextTrack', 'captureStream',
88
-
89
- // Modern JavaScript APIs
90
- 'structuredClone', 'reportError', 'WeakRef', 'deref', 'FinalizationRegistry',
91
- 'AggregateError', 'Promise', 'allSettled', 'any', 'race', 'resolve', 'reject',
92
- 'AsyncIterator', 'Symbol', 'iterator', 'asyncIterator', 'BigInt', 'asIntN', 'asUintN',
93
-
94
- // Intl APIs
95
- 'Intl', 'DateTimeFormat', 'format', 'formatToParts', 'resolvedOptions',
96
- 'NumberFormat', 'Collator', 'compare', 'PluralRules', 'select',
97
- 'RelativeTimeFormat', 'ListFormat', 'Locale', 'getCanonicalLocales',
98
- 'DisplayNames', 'of', 'Segmenter', 'segment',
99
-
100
- // Streams API
101
- 'ReadableStream', 'WritableStream', 'TransformStream', 'getReader', 'getWriter',
102
- 'readable', 'writable', 'pipeThrough', 'pipeTo', 'tee',
103
-
104
- // Web Components
105
- 'customElements', 'define', 'get', 'whenDefined', 'upgrade', 'ShadowRoot',
106
- 'attachShadow', 'shadowRoot', 'HTMLTemplateElement', 'content', 'HTMLSlotElement',
107
-
108
- // Pointer Events
109
- 'PointerEvent', 'pointerId', 'pointerType', 'isPrimary', 'setPointerCapture',
110
- 'releasePointerCapture', 'hasPointerCapture',
111
-
112
- // Touch Events
113
- 'TouchEvent', 'touches', 'targetTouches', 'changedTouches', 'Touch',
114
-
115
- // Gamepad API
116
- 'navigator.getGamepads', 'Gamepad', 'GamepadButton', 'pressed', 'value',
117
-
118
- // Battery API
119
- 'navigator.getBattery', 'BatteryManager', 'charging', 'chargingTime',
120
- 'dischargingTime', 'level',
121
-
122
- // Device APIs
123
- 'DeviceOrientationEvent', 'alpha', 'beta', 'gamma', 'absolute',
124
- 'DeviceMotionEvent', 'acceleration', 'accelerationIncludingGravity',
125
- 'rotationRate', 'interval'
126
- ]);
127
-
128
- private readonly CSS_PROPERTIES = new Set([
129
- // Container Queries
130
- 'container-type', 'container-name', 'container', 'container-query-length',
131
-
132
- // Grid Layout
133
- 'display', 'grid', 'inline-grid', 'grid-template-columns', 'grid-template-rows',
134
- 'grid-template-areas', 'grid-template', 'grid-column-start', 'grid-column-end',
135
- 'grid-row-start', 'grid-row-end', 'grid-column', 'grid-row', 'grid-area',
136
- 'grid-auto-columns', 'grid-auto-rows', 'grid-auto-flow', 'gap', 'grid-gap',
137
- 'column-gap', 'row-gap', 'grid-column-gap', 'grid-row-gap',
138
-
139
- // Flexbox
140
- 'flex', 'inline-flex', 'flex-direction', 'flex-wrap', 'flex-flow',
141
- 'justify-content', 'align-items', 'align-content', 'align-self',
142
- 'flex-grow', 'flex-shrink', 'flex-basis', 'order',
143
-
144
- // Modern Layout
145
- 'aspect-ratio', 'object-fit', 'object-position', 'place-items', 'place-content',
146
- 'place-self', 'justify-items', 'justify-self', 'align-tracks', 'justify-tracks',
147
-
148
- // Visual Effects
149
- 'backdrop-filter', 'filter', 'mix-blend-mode', 'isolation', 'clip-path',
150
- 'mask', 'mask-image', 'mask-mode', 'mask-repeat', 'mask-position',
151
- 'mask-clip', 'mask-origin', 'mask-size', 'mask-composite',
152
-
153
- // Color & Appearance
154
- 'color-scheme', 'accent-color', 'color-mix', 'color-contrast',
155
- 'forced-color-adjust', 'print-color-adjust',
156
-
157
- // Scrolling
158
- 'scroll-behavior', 'scroll-snap-type', 'scroll-snap-align', 'scroll-snap-stop',
159
- 'scroll-margin', 'scroll-margin-top', 'scroll-margin-right', 'scroll-margin-bottom',
160
- 'scroll-margin-left', 'scroll-padding', 'scroll-padding-top', 'scroll-padding-right',
161
- 'scroll-padding-bottom', 'scroll-padding-left', 'overscroll-behavior',
162
- 'overscroll-behavior-x', 'overscroll-behavior-y', 'scroll-timeline',
163
- 'scroll-timeline-name', 'scroll-timeline-axis',
164
-
165
- // Interaction
166
- 'touch-action', 'user-select', 'pointer-events', 'cursor',
167
-
168
- // CSS Custom Properties & Functions
169
- '--', 'var(', 'calc(', 'clamp(', 'min(', 'max(', 'minmax(',
170
-
171
- // Transforms & Animations
172
- 'transform', 'transform-origin', 'transform-style', 'perspective',
173
- 'perspective-origin', 'backface-visibility', 'animation', 'animation-name',
174
- 'animation-duration', 'animation-timing-function', 'animation-delay',
175
- 'animation-iteration-count', 'animation-direction', 'animation-fill-mode',
176
- 'animation-play-state', 'animation-timeline', 'transition', 'transition-property',
177
- 'transition-duration', 'transition-timing-function', 'transition-delay',
178
- 'will-change', 'contain', 'content-visibility',
179
-
180
- // Typography
181
- 'font-display', 'font-variation-settings', 'font-optical-sizing',
182
- 'font-palette', 'text-decoration-thickness', 'text-decoration-skip-ink',
183
- 'text-underline-offset', 'text-underline-position', 'hanging-punctuation',
184
- 'hyphens', 'line-break', 'word-break', 'overflow-wrap', 'text-overflow',
185
- 'white-space', 'tab-size', 'writing-mode', 'text-orientation',
186
-
187
- // Layout & Positioning
188
- 'position', 'top', 'right', 'bottom', 'left', 'z-index', 'inset',
189
- 'inset-block', 'inset-inline', 'width', 'height', 'min-width', 'min-height',
190
- 'max-width', 'max-height', 'margin', 'margin-top', 'margin-right',
191
- 'margin-bottom', 'margin-left', 'padding', 'padding-top', 'padding-right',
192
- 'padding-bottom', 'padding-left', 'border', 'border-radius', 'outline',
193
- 'box-sizing', 'overflow', 'overflow-x', 'overflow-y', 'resize',
194
-
195
- // Logical Properties
196
- 'margin-block', 'margin-inline', 'padding-block', 'padding-inline',
197
- 'border-block', 'border-inline', 'inset-block-start', 'inset-block-end',
198
- 'inset-inline-start', 'inset-inline-end'
199
- ]);
200
-
201
- private readonly CSS_SELECTORS = new Set([
202
- ':has(', ':is(', ':where(', ':not(', ':focus-visible', ':focus-within',
203
- ':target-within', ':any-link', ':local-link', ':scope', ':current',
204
- ':past', ':future', ':playing', ':paused', ':seeking', ':buffering',
205
- ':stalled', ':muted', ':volume-locked', ':fullscreen', ':picture-in-picture',
206
- ':user-invalid', ':user-valid', ':blank', ':placeholder-shown',
207
- '::backdrop', '::placeholder', '::marker', '::selection', '::first-letter',
208
- '::first-line', '::before', '::after', '::file-selector-button'
209
- ]);
210
-
211
- private readonly HTML_ELEMENTS = new Set([
212
- // Modern semantic elements
213
- 'dialog', 'details', 'summary', 'main', 'article', 'section', 'nav', 'aside',
214
- 'header', 'footer', 'figure', 'figcaption', 'time', 'mark', 'progress', 'meter',
215
-
216
- // Media elements
217
- 'canvas', 'video', 'audio', 'source', 'track', 'embed', 'object', 'picture',
218
-
219
- // Form elements
220
- 'datalist', 'output', 'keygen', 'fieldset', 'legend',
221
-
222
- // Interactive elements
223
- 'slot', 'template'
224
- ]);
225
-
226
- private readonly HTML_ATTRIBUTES = new Set([
227
- 'loading', 'decoding', 'fetchpriority', 'enterkeyhint', 'inputmode',
228
- 'autocomplete', 'autofocus', 'capture', 'crossorigin', 'dirname',
229
- 'download', 'draggable', 'enctype', 'formaction', 'formenctype',
230
- 'formmethod', 'formnovalidate', 'formtarget', 'hidden', 'integrity',
231
- 'is', 'itemid', 'itemprop', 'itemref', 'itemscope', 'itemtype',
232
- 'kind', 'label', 'lang', 'list', 'loop', 'max', 'maxlength', 'media',
233
- 'method', 'min', 'minlength', 'multiple', 'muted', 'novalidate',
234
- 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster',
235
- 'preload', 'readonly', 'referrerpolicy', 'rel', 'required', 'reversed',
236
- 'rows', 'rowspan', 'sandbox', 'scope', 'selected', 'shape', 'size',
237
- 'sizes', 'span', 'spellcheck', 'srcdoc', 'srclang', 'srcset', 'start',
238
- 'step', 'target', 'translate', 'type', 'usemap', 'value', 'wrap'
239
- ]);
240
-
241
- canParse(filePath: string): boolean {
242
- return /\.(js|ts|html|css)$/.test(filePath);
243
- }
244
-
245
- async parseFeatures(content: string, filePath: string): Promise<DetectedFeature[]> {
246
- const extension = this.getFileExtension(filePath);
247
-
248
- switch (extension) {
249
- case '.js':
250
- case '.ts':
251
- return this.parseJavaScript(content, filePath, extension === '.ts');
252
- case '.css':
253
- return this.parseCSS(content, filePath);
254
- case '.html':
255
- return await this.parseHTML(content, filePath);
256
- default:
257
- return [];
258
- }
259
- }
260
-
261
- private async parseJavaScript(content: string, filePath: string, isTypeScript: boolean): Promise<DetectedFeature[]> {
262
- const features: DetectedFeature[] = [];
263
-
264
- try {
265
- const ast = parseBabel(content, {
266
- sourceType: 'module',
267
- plugins: [
268
- 'typescript' as any,
269
- 'decorators-legacy' as any,
270
- 'classProperties' as any,
271
- 'objectRestSpread' as any,
272
- 'asyncGenerators' as any,
273
- 'functionBind' as any,
274
- 'exportDefaultFrom' as any,
275
- 'exportNamespaceFrom' as any,
276
- 'dynamicImport' as any,
277
- 'nullishCoalescingOperator',
278
- 'optionalChaining',
279
- 'topLevelAwait',
280
- 'privateIn',
281
- 'importMeta'
282
- ].filter(plugin => isTypeScript || plugin !== 'typescript')
283
- });
284
-
285
- traverse(ast, {
286
- // Extract Web API member expressions
287
- MemberExpression: (path: any) => {
288
- const feature = this.extractWebAPIFeature(path.node, content);
289
- if (feature) {
290
- features.push({ ...feature, file: filePath });
291
- }
292
- },
293
-
294
- // Extract Web API function calls
295
- CallExpression: (path: any) => {
296
- const feature = this.extractWebAPICall(path.node, content);
297
- if (feature) {
298
- features.push({ ...feature, file: filePath });
299
- }
300
- },
301
-
302
- // Modern JavaScript syntax features
303
- OptionalMemberExpression: (path: any) => {
304
- features.push({
305
- feature: 'optional-chaining',
306
- type: 'js',
307
- context: this.getContext(content, path.node.loc?.start.line || 0),
308
- line: path.node.loc?.start.line || 0,
309
- column: path.node.loc?.start.column || 0,
310
- file: filePath
311
- });
312
- },
313
-
314
- OptionalCallExpression: (path: any) => {
315
- features.push({
316
- feature: 'optional-chaining',
317
- type: 'js',
318
- context: this.getContext(content, path.node.loc?.start.line || 0),
319
- line: path.node.loc?.start.line || 0,
320
- column: path.node.loc?.start.column || 0,
321
- file: filePath
322
- });
323
- },
324
-
325
- // Nullish coalescing
326
- LogicalExpression: (path: any) => {
327
- if (path.node.operator === '??') {
328
- features.push({
329
- feature: 'nullish-coalescing',
330
- type: 'js',
331
- context: this.getContext(content, path.node.loc?.start.line || 0),
332
- line: path.node.loc?.start.line || 0,
333
- column: path.node.loc?.start.column || 0,
334
- file: filePath
335
- });
336
- }
337
- },
338
-
339
- // Private class fields
340
- ClassPrivateProperty: (path: any) => {
341
- features.push({
342
- feature: 'private-fields',
343
- type: 'js',
344
- context: this.getContext(content, path.node.loc?.start.line || 0),
345
- line: path.node.loc?.start.line || 0,
346
- column: path.node.loc?.start.column || 0,
347
- file: filePath
348
- });
349
- },
350
-
351
- ClassPrivateMethod: (path: any) => {
352
- features.push({
353
- feature: 'private-methods',
354
- type: 'js',
355
- context: this.getContext(content, path.node.loc?.start.line || 0),
356
- line: path.node.loc?.start.line || 0,
357
- column: path.node.loc?.start.column || 0,
358
- file: filePath
359
- });
360
- },
361
-
362
- // Top-level await
363
- AwaitExpression: (path: any) => {
364
- if (this.isTopLevelAwait(path)) {
365
- features.push({
366
- feature: 'top-level-await',
367
- type: 'js',
368
- context: this.getContext(content, path.node.loc?.start.line || 0),
369
- line: path.node.loc?.start.line || 0,
370
- column: path.node.loc?.start.column || 0,
371
- file: filePath
372
- });
373
- }
374
- },
375
-
376
- // Dynamic imports
377
- Import: (path: any) => {
378
- features.push({
379
- feature: 'dynamic-import',
380
- type: 'js',
381
- context: this.getContext(content, path.node.loc?.start.line || 0),
382
- line: path.node.loc?.start.line || 0,
383
- column: path.node.loc?.start.column || 0,
384
- file: filePath
385
- });
386
- },
387
-
388
- // BigInt literals
389
- BigIntLiteral: (path: any) => {
390
- features.push({
391
- feature: 'bigint',
392
- type: 'js',
393
- context: this.getContext(content, path.node.loc?.start.line || 0),
394
- line: path.node.loc?.start.line || 0,
395
- column: path.node.loc?.start.column || 0,
396
- file: filePath
397
- });
398
- },
399
-
400
- // Numeric separators
401
- NumericLiteral: (path: any) => {
402
- if ((path.node.extra as any)?.raw?.includes('_')) {
403
- features.push({
404
- feature: 'numeric-separators',
405
- type: 'js',
406
- context: this.getContext(content, path.node.loc?.start.line || 0),
407
- line: path.node.loc?.start.line || 0,
408
- column: path.node.loc?.start.column || 0,
409
- file: filePath
410
- });
411
- }
412
- }
413
- });
414
-
415
- } catch (error) {
416
- console.warn(`Warning: Could not parse JavaScript file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
417
- }
418
-
419
- return features;
420
- }
421
-
422
- private async parseCSS(content: string, filePath: string): Promise<DetectedFeature[]> {
423
- const features: DetectedFeature[] = [];
424
-
425
- try {
426
- const root = postcss.parse(content);
427
-
428
- // Extract CSS properties
429
- root.walkDecls((decl: any) => {
430
- if (this.CSS_PROPERTIES.has(decl.prop) || decl.prop.startsWith('--')) {
431
- features.push({
432
- feature: decl.prop,
433
- type: 'css',
434
- context: `${decl.prop}: ${decl.value}`,
435
- line: decl.source?.start?.line || 0,
436
- column: decl.source?.start?.column || 0,
437
- file: filePath
438
- });
439
- }
440
-
441
- // Check for CSS functions in values
442
- if (decl.value.includes('var(') || decl.value.includes('calc(') ||
443
- decl.value.includes('clamp(') || decl.value.includes('min(') ||
444
- decl.value.includes('max(')) {
445
- const func = this.extractCSSFunction(decl.value);
446
- if (func) {
447
- features.push({
448
- feature: func,
449
- type: 'css',
450
- context: `${decl.prop}: ${decl.value}`,
451
- line: decl.source?.start?.line || 0,
452
- column: decl.source?.start?.column || 0,
453
- file: filePath
454
- });
455
- }
456
- }
457
- });
458
-
459
- // Extract CSS selectors
460
- root.walkRules((rule: any) => {
461
- this.CSS_SELECTORS.forEach(selector => {
462
- if (rule.selector.includes(selector)) {
463
- features.push({
464
- feature: selector.replace('(', '()'),
465
- type: 'css',
466
- context: rule.selector,
467
- line: rule.source?.start?.line || 0,
468
- column: rule.source?.start?.column || 0,
469
- file: filePath
470
- });
471
- }
472
- });
473
- });
474
-
475
- // Extract at-rules
476
- root.walkAtRules((atRule: any) => {
477
- const atRuleName = `@${atRule.name}`;
478
- features.push({
479
- feature: atRuleName,
480
- type: 'css',
481
- context: `${atRuleName} ${atRule.params}`,
482
- line: atRule.source?.start?.line || 0,
483
- column: atRule.source?.start?.column || 0,
484
- file: filePath
485
- });
486
- });
487
-
488
- } catch (error) {
489
- console.warn(`Warning: Could not parse CSS file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
490
- }
491
-
492
- return features;
493
- }
494
-
495
- private async parseHTML(content: string, filePath: string): Promise<DetectedFeature[]> {
496
- const features: DetectedFeature[] = [];
497
- const lines = content.split('\n');
498
-
499
- // Parse HTML elements and attributes
500
- const elementRegex = /<(\w+)([^>]*)>/g;
501
-
502
- lines.forEach((line, index) => {
503
- let match: RegExpExecArray | null;
504
- while ((match = elementRegex.exec(line)) !== null) {
505
- const tagName = match[1];
506
- const attributes = match[2];
507
-
508
- // Check for modern HTML elements
509
- if (tagName && this.HTML_ELEMENTS.has(tagName)) {
510
- features.push({
511
- feature: tagName!,
512
- type: 'html',
513
- context: line.trim(),
514
- line: index + 1,
515
- column: match.index,
516
- file: filePath
517
- });
518
- }
519
-
520
- // Check for modern HTML attributes
521
- if (attributes) {
522
- const modernAttrs = this.extractHTMLAttributes(attributes);
523
- modernAttrs.forEach(attr => {
524
- features.push({
525
- feature: attr,
526
- type: 'html',
527
- context: line.trim(),
528
- line: index + 1,
529
- column: match?.index || 0,
530
- file: filePath
531
- });
532
- });
533
- }
534
- }
535
- });
536
-
537
- // Extract inline CSS and JavaScript
538
- const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
539
- const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
540
-
541
- // Parse inline styles
542
- let styleMatch;
543
- while ((styleMatch = styleRegex.exec(content)) !== null) {
544
- const cssContent = styleMatch[1];
545
- const cssFeatures = cssContent ? await this.parseInlineCSS(cssContent, filePath) : [];
546
- features.push(...cssFeatures);
547
- }
548
-
549
- // Parse inline scripts
550
- let scriptMatch;
551
- while ((scriptMatch = scriptRegex.exec(content)) !== null) {
552
- const jsContent = scriptMatch[1];
553
- const jsFeatures = jsContent ? await this.parseInlineJS(jsContent, filePath) : [];
554
- features.push(...jsFeatures);
555
- }
556
-
557
- return features;
558
- }
559
-
560
- private async parseInlineCSS(cssContent: string, filePath: string): Promise<DetectedFeature[]> {
561
- try {
562
- return await this.parseCSS(cssContent, filePath);
563
- } catch (error) {
564
- console.warn(`Warning: Could not parse inline CSS in ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
565
- return [];
566
- }
567
- }
568
-
569
- private async parseInlineJS(jsContent: string, filePath: string): Promise<DetectedFeature[]> {
570
- try {
571
- return await this.parseJavaScript(jsContent, filePath, false);
572
- } catch (error) {
573
- console.warn(`Warning: Could not parse inline JavaScript in ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
574
- return [];
575
- }
576
- }
577
-
578
- private extractWebAPIFeature(node: t.MemberExpression, content: string): DetectedFeature | null {
579
- const apiName = this.getMemberExpressionName(node);
580
-
581
- if (this.WEB_PLATFORM_APIS.has(apiName)) {
582
- return {
583
- feature: apiName,
584
- type: 'js',
585
- context: this.getContext(content, node.loc?.start.line || 0),
586
- line: node.loc?.start.line || 0,
587
- column: node.loc?.start.column || 0
588
- };
589
- }
590
-
591
- return null;
592
- }
593
-
594
- private extractWebAPICall(node: t.CallExpression, content: string): DetectedFeature | null {
595
- let apiName = '';
596
-
597
- if (t.isIdentifier(node.callee)) {
598
- apiName = node.callee.name;
599
- } else if (t.isMemberExpression(node.callee)) {
600
- apiName = this.getMemberExpressionName(node.callee);
601
- }
602
-
603
- if (this.WEB_PLATFORM_APIS.has(apiName)) {
604
- return {
605
- feature: apiName,
606
- type: 'js',
607
- context: this.getContext(content, node.loc?.start.line || 0),
608
- line: node.loc?.start.line || 0,
609
- column: node.loc?.start.column || 0
610
- };
611
- }
612
-
613
- return null;
614
- }
615
-
616
- private extractCSSFunction(value: string): string | null {
617
- const functions = ['var(', 'calc(', 'clamp(', 'min(', 'max(', 'minmax('];
618
- for (const func of functions) {
619
- if (value.includes(func)) {
620
- return func.replace('(', '()');
621
- }
622
- }
623
- return null;
624
- }
625
-
626
- private extractHTMLAttributes(attributes: string): string[] {
627
- const modernAttrs: string[] = [];
628
-
629
- this.HTML_ATTRIBUTES.forEach(attr => {
630
- const attrRegex = new RegExp(`\\b${attr}\\b`, 'i');
631
- if (attrRegex.test(attributes)) {
632
- modernAttrs.push(attr);
633
- }
634
- });
635
-
636
- return modernAttrs;
637
- }
638
-
639
- private getMemberExpressionName(node: t.MemberExpression): string {
640
- const parts: string[] = [];
641
-
642
- const traverse = (n: t.Expression): void => {
643
- if (t.isIdentifier(n)) {
644
- parts.unshift(n.name);
645
- } else if (t.isMemberExpression(n)) {
646
- if (t.isIdentifier(n.property)) {
647
- parts.unshift(n.property.name);
648
- }
649
- traverse(n.object);
650
- }
651
- };
652
-
653
- traverse(node);
654
- return parts.join('.');
655
- }
656
-
657
- private isTopLevelAwait(path: any): boolean {
658
- let parent = path.parent;
659
- while (parent) {
660
- if (t.isFunction(parent) || t.isArrowFunctionExpression(parent)) {
661
- return false;
662
- }
663
- parent = path.parentPath?.parent;
664
- }
665
- return true;
666
- }
667
-
668
- private getFileExtension(filePath: string): string {
669
- const match = filePath.match(/\.[^.]+$/);
670
- return match ? match[0] : '';
671
- }
672
-
673
- private getContext(content: string, line: number): string {
674
- const lines = content.split('\n');
675
- const targetLine = lines[line - 1] || '';
676
- return targetLine.trim();
677
- }
678
-
679
- getSupportedExtensions(): string[] {
680
- return ['.js', '.ts', '.html', '.css'];
681
- }
682
-
683
- getName(): string {
684
- return 'VanillaParser';
685
- }
686
- }
1
+ import { Parser } from './parser.js';
2
+ import type { DetectedFeature } from '../types/index.js';
3
+ import { parse as parseBabel } from '@babel/parser';
4
+ import traverse from '@babel/traverse';
5
+ import * as t from '@babel/types';
6
+ import postcss from 'postcss';
7
+
8
+ /**
9
+ * Vanilla JavaScript/CSS/HTML parser - extracts ALL web platform features
10
+ * Handles .js, .ts, .html, and .css files with comprehensive feature detection
11
+ */
12
+ export class VanillaParser extends Parser {
13
+ private readonly WEB_PLATFORM_APIS = new Set([
14
+ // Canvas APIs
15
+ 'getContext', 'CanvasRenderingContext2D', 'WebGLRenderingContext', 'WebGL2RenderingContext',
16
+ 'OffscreenCanvas', 'ImageBitmap', 'createImageBitmap', 'Path2D', 'ImageData',
17
+ 'CanvasGradient', 'CanvasPattern', 'TextMetrics',
18
+
19
+ // WebGL specific
20
+ 'createShader', 'shaderSource', 'compileShader', 'createProgram', 'attachShader',
21
+ 'linkProgram', 'useProgram', 'getAttribLocation', 'getUniformLocation',
22
+ 'enableVertexAttribArray', 'vertexAttribPointer', 'uniform1f', 'uniform2f',
23
+ 'uniform3f', 'uniform4f', 'uniformMatrix4fv', 'drawArrays', 'drawElements',
24
+
25
+ // WebRTC APIs
26
+ 'RTCPeerConnection', 'RTCDataChannel', 'RTCSessionDescription', 'RTCIceCandidate',
27
+ 'getUserMedia', 'getDisplayMedia', 'MediaStream', 'MediaStreamTrack',
28
+ 'RTCRtpSender', 'RTCRtpReceiver', 'RTCStatsReport', 'RTCIceServer',
29
+
30
+ // WebAssembly
31
+ 'WebAssembly', 'instantiate', 'compile', 'validate', 'Module', 'Instance',
32
+ 'Memory', 'Table', 'Global', 'CompileError', 'LinkError', 'RuntimeError',
33
+
34
+ // Service Workers & PWA
35
+ 'ServiceWorker', 'serviceWorker', 'register', 'unregister', 'update',
36
+ 'Cache', 'caches', 'open', 'match', 'matchAll', 'add', 'addAll', 'put', 'delete',
37
+ 'PushManager', 'subscribe', 'getSubscription', 'permissionState',
38
+ 'Notification', 'showNotification', 'getNotifications', 'requestPermission',
39
+ 'BackgroundSync', 'sync', 'getTags', 'BackgroundFetch',
40
+
41
+ // DOM APIs
42
+ 'querySelector', 'querySelectorAll', 'getElementById', 'getElementsByClassName',
43
+ 'getElementsByTagName', 'createElement', 'createTextNode', 'createDocumentFragment',
44
+ 'addEventListener', 'removeEventListener', 'dispatchEvent', 'CustomEvent',
45
+ 'MutationObserver', 'observe', 'disconnect', 'takeRecords',
46
+ 'ResizeObserver', 'IntersectionObserver', 'PerformanceObserver',
47
+ 'AbortController', 'AbortSignal', 'abort', 'signal',
48
+ 'FormData', 'append', 'set', 'get', 'getAll', 'has', 'entries', 'keys', 'values',
49
+ 'URLSearchParams', 'URL', 'createObjectURL', 'revokeObjectURL',
50
+
51
+ // Fetch API
52
+ 'fetch', 'Request', 'Response', 'Headers', 'clone', 'json', 'text', 'blob',
53
+ 'arrayBuffer', 'formData', 'ok', 'status', 'statusText', 'redirected',
54
+
55
+ // File APIs
56
+ 'Blob', 'File', 'FileReader', 'FileList', 'readAsText', 'readAsDataURL',
57
+ 'readAsArrayBuffer', 'readAsBinaryString', 'slice',
58
+
59
+ // Web APIs
60
+ 'navigator', 'geolocation', 'getCurrentPosition', 'watchPosition', 'clearWatch',
61
+ 'permissions', 'query', 'clipboard', 'writeText', 'readText', 'write', 'read',
62
+ 'share', 'canShare', 'requestAnimationFrame', 'cancelAnimationFrame',
63
+ 'requestIdleCallback', 'cancelIdleCallback', 'setTimeout', 'setInterval',
64
+ 'clearTimeout', 'clearInterval', 'queueMicrotask',
65
+
66
+ // Storage APIs
67
+ 'localStorage', 'sessionStorage', 'getItem', 'setItem', 'removeItem', 'clear',
68
+ 'indexedDB', 'open', 'deleteDatabase', 'cmp', 'IDBDatabase', 'IDBTransaction',
69
+ 'IDBObjectStore', 'IDBIndex', 'IDBCursor', 'IDBKeyRange',
70
+
71
+ // Crypto APIs
72
+ 'crypto', 'getRandomValues', 'randomUUID', 'subtle', 'encrypt', 'decrypt',
73
+ 'sign', 'verify', 'digest', 'generateKey', 'importKey', 'exportKey',
74
+
75
+ // Performance APIs
76
+ 'performance', 'now', 'mark', 'measure', 'getEntries', 'getEntriesByName',
77
+ 'getEntriesByType', 'clearMarks', 'clearMeasures', 'timing', 'navigation',
78
+
79
+ // Audio/Video APIs
80
+ 'AudioContext', 'createOscillator', 'createGain', 'createAnalyser',
81
+ 'createBiquadFilter', 'createBufferSource', 'createMediaElementSource',
82
+ 'createScriptProcessor', 'createDynamicsCompressor', 'createConvolver',
83
+ 'MediaRecorder', 'start', 'stop', 'pause', 'resume', 'requestData',
84
+ 'MediaSource', 'SourceBuffer', 'appendBuffer', 'remove', 'abort',
85
+ 'HTMLMediaElement', 'HTMLAudioElement', 'HTMLVideoElement', 'play', 'pause',
86
+ 'load', 'canPlayType', 'addTextTrack', 'captureStream',
87
+
88
+ // Modern JavaScript APIs
89
+ 'structuredClone', 'reportError', 'WeakRef', 'deref', 'FinalizationRegistry',
90
+ 'AggregateError', 'Promise', 'allSettled', 'any', 'race', 'resolve', 'reject',
91
+ 'AsyncIterator', 'Symbol', 'iterator', 'asyncIterator', 'BigInt', 'asIntN', 'asUintN',
92
+
93
+ // Intl APIs
94
+ 'Intl', 'DateTimeFormat', 'format', 'formatToParts', 'resolvedOptions',
95
+ 'NumberFormat', 'Collator', 'compare', 'PluralRules', 'select',
96
+ 'RelativeTimeFormat', 'ListFormat', 'Locale', 'getCanonicalLocales',
97
+ 'DisplayNames', 'of', 'Segmenter', 'segment',
98
+
99
+ // Streams API
100
+ 'ReadableStream', 'WritableStream', 'TransformStream', 'getReader', 'getWriter',
101
+ 'readable', 'writable', 'pipeThrough', 'pipeTo', 'tee',
102
+
103
+ // Web Components
104
+ 'customElements', 'define', 'get', 'whenDefined', 'upgrade', 'ShadowRoot',
105
+ 'attachShadow', 'shadowRoot', 'HTMLTemplateElement', 'content', 'HTMLSlotElement',
106
+
107
+ // Pointer Events
108
+ 'PointerEvent', 'pointerId', 'pointerType', 'isPrimary', 'setPointerCapture',
109
+ 'releasePointerCapture', 'hasPointerCapture',
110
+
111
+ // Touch Events
112
+ 'TouchEvent', 'touches', 'targetTouches', 'changedTouches', 'Touch',
113
+
114
+ // Gamepad API
115
+ 'navigator.getGamepads', 'Gamepad', 'GamepadButton', 'pressed', 'value',
116
+
117
+ // Battery API
118
+ 'navigator.getBattery', 'BatteryManager', 'charging', 'chargingTime',
119
+ 'dischargingTime', 'level',
120
+
121
+ // Device APIs
122
+ 'DeviceOrientationEvent', 'alpha', 'beta', 'gamma', 'absolute',
123
+ 'DeviceMotionEvent', 'acceleration', 'accelerationIncludingGravity',
124
+ 'rotationRate', 'interval'
125
+ ]);
126
+
127
+ private readonly CSS_PROPERTIES = new Set([
128
+ // Container Queries
129
+ 'container-type', 'container-name', 'container', 'container-query-length',
130
+
131
+ // Grid Layout
132
+ 'display', 'grid', 'inline-grid', 'grid-template-columns', 'grid-template-rows',
133
+ 'grid-template-areas', 'grid-template', 'grid-column-start', 'grid-column-end',
134
+ 'grid-row-start', 'grid-row-end', 'grid-column', 'grid-row', 'grid-area',
135
+ 'grid-auto-columns', 'grid-auto-rows', 'grid-auto-flow', 'gap', 'grid-gap',
136
+ 'column-gap', 'row-gap', 'grid-column-gap', 'grid-row-gap',
137
+
138
+ // Flexbox
139
+ 'flex', 'inline-flex', 'flex-direction', 'flex-wrap', 'flex-flow',
140
+ 'justify-content', 'align-items', 'align-content', 'align-self',
141
+ 'flex-grow', 'flex-shrink', 'flex-basis', 'order',
142
+
143
+ // Modern Layout
144
+ 'aspect-ratio', 'object-fit', 'object-position', 'place-items', 'place-content',
145
+ 'place-self', 'justify-items', 'justify-self', 'align-tracks', 'justify-tracks',
146
+
147
+ // Visual Effects
148
+ 'backdrop-filter', 'filter', 'mix-blend-mode', 'isolation', 'clip-path',
149
+ 'mask', 'mask-image', 'mask-mode', 'mask-repeat', 'mask-position',
150
+ 'mask-clip', 'mask-origin', 'mask-size', 'mask-composite',
151
+
152
+ // Color & Appearance
153
+ 'color-scheme', 'accent-color', 'color-mix', 'color-contrast',
154
+ 'forced-color-adjust', 'print-color-adjust',
155
+
156
+ // Scrolling
157
+ 'scroll-behavior', 'scroll-snap-type', 'scroll-snap-align', 'scroll-snap-stop',
158
+ 'scroll-margin', 'scroll-margin-top', 'scroll-margin-right', 'scroll-margin-bottom',
159
+ 'scroll-margin-left', 'scroll-padding', 'scroll-padding-top', 'scroll-padding-right',
160
+ 'scroll-padding-bottom', 'scroll-padding-left', 'overscroll-behavior',
161
+ 'overscroll-behavior-x', 'overscroll-behavior-y', 'scroll-timeline',
162
+ 'scroll-timeline-name', 'scroll-timeline-axis',
163
+
164
+ // Interaction
165
+ 'touch-action', 'user-select', 'pointer-events', 'cursor',
166
+
167
+ // CSS Custom Properties & Functions
168
+ '--', 'var(', 'calc(', 'clamp(', 'min(', 'max(', 'minmax(',
169
+
170
+ // Transforms & Animations
171
+ 'transform', 'transform-origin', 'transform-style', 'perspective',
172
+ 'perspective-origin', 'backface-visibility', 'animation', 'animation-name',
173
+ 'animation-duration', 'animation-timing-function', 'animation-delay',
174
+ 'animation-iteration-count', 'animation-direction', 'animation-fill-mode',
175
+ 'animation-play-state', 'animation-timeline', 'transition', 'transition-property',
176
+ 'transition-duration', 'transition-timing-function', 'transition-delay',
177
+ 'will-change', 'contain', 'content-visibility',
178
+
179
+ // Typography
180
+ 'font-display', 'font-variation-settings', 'font-optical-sizing',
181
+ 'font-palette', 'text-decoration-thickness', 'text-decoration-skip-ink',
182
+ 'text-underline-offset', 'text-underline-position', 'hanging-punctuation',
183
+ 'hyphens', 'line-break', 'word-break', 'overflow-wrap', 'text-overflow',
184
+ 'white-space', 'tab-size', 'writing-mode', 'text-orientation',
185
+
186
+ // Layout & Positioning
187
+ 'position', 'top', 'right', 'bottom', 'left', 'z-index', 'inset',
188
+ 'inset-block', 'inset-inline', 'width', 'height', 'min-width', 'min-height',
189
+ 'max-width', 'max-height', 'margin', 'margin-top', 'margin-right',
190
+ 'margin-bottom', 'margin-left', 'padding', 'padding-top', 'padding-right',
191
+ 'padding-bottom', 'padding-left', 'border', 'border-radius', 'outline',
192
+ 'box-sizing', 'overflow', 'overflow-x', 'overflow-y', 'resize',
193
+
194
+ // Logical Properties
195
+ 'margin-block', 'margin-inline', 'padding-block', 'padding-inline',
196
+ 'border-block', 'border-inline', 'inset-block-start', 'inset-block-end',
197
+ 'inset-inline-start', 'inset-inline-end'
198
+ ]);
199
+
200
+ private readonly CSS_SELECTORS = new Set([
201
+ ':has(', ':is(', ':where(', ':not(', ':focus-visible', ':focus-within',
202
+ ':target-within', ':any-link', ':local-link', ':scope', ':current',
203
+ ':past', ':future', ':playing', ':paused', ':seeking', ':buffering',
204
+ ':stalled', ':muted', ':volume-locked', ':fullscreen', ':picture-in-picture',
205
+ ':user-invalid', ':user-valid', ':blank', ':placeholder-shown',
206
+ '::backdrop', '::placeholder', '::marker', '::selection', '::first-letter',
207
+ '::first-line', '::before', '::after', '::file-selector-button'
208
+ ]);
209
+
210
+ private readonly HTML_ELEMENTS = new Set([
211
+ // Modern semantic elements
212
+ 'dialog', 'details', 'summary', 'main', 'article', 'section', 'nav', 'aside',
213
+ 'header', 'footer', 'figure', 'figcaption', 'time', 'mark', 'progress', 'meter',
214
+
215
+ // Media elements
216
+ 'canvas', 'video', 'audio', 'source', 'track', 'embed', 'object', 'picture',
217
+
218
+ // Form elements
219
+ 'datalist', 'output', 'keygen', 'fieldset', 'legend',
220
+
221
+ // Interactive elements
222
+ 'slot', 'template'
223
+ ]);
224
+
225
+ private readonly HTML_ATTRIBUTES = new Set([
226
+ 'loading', 'decoding', 'fetchpriority', 'enterkeyhint', 'inputmode',
227
+ 'autocomplete', 'autofocus', 'capture', 'crossorigin', 'dirname',
228
+ 'download', 'draggable', 'enctype', 'formaction', 'formenctype',
229
+ 'formmethod', 'formnovalidate', 'formtarget', 'hidden', 'integrity',
230
+ 'is', 'itemid', 'itemprop', 'itemref', 'itemscope', 'itemtype',
231
+ 'kind', 'label', 'lang', 'list', 'loop', 'max', 'maxlength', 'media',
232
+ 'method', 'min', 'minlength', 'multiple', 'muted', 'novalidate',
233
+ 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster',
234
+ 'preload', 'readonly', 'referrerpolicy', 'rel', 'required', 'reversed',
235
+ 'rows', 'rowspan', 'sandbox', 'scope', 'selected', 'shape', 'size',
236
+ 'sizes', 'span', 'spellcheck', 'srcdoc', 'srclang', 'srcset', 'start',
237
+ 'step', 'target', 'translate', 'type', 'usemap', 'value', 'wrap'
238
+ ]);
239
+
240
+ canParse(filePath: string): boolean {
241
+ return /\.(js|ts|html|css)$/.test(filePath);
242
+ }
243
+
244
+ async parseFeatures(content: string, filePath: string): Promise<DetectedFeature[]> {
245
+ const extension = this.getFileExtension(filePath);
246
+
247
+ switch (extension) {
248
+ case '.js':
249
+ case '.ts':
250
+ return this.parseJavaScript(content, filePath, extension === '.ts');
251
+ case '.css':
252
+ return this.parseCSS(content, filePath);
253
+ case '.html':
254
+ return await this.parseHTML(content, filePath);
255
+ default:
256
+ return [];
257
+ }
258
+ }
259
+
260
+ private async parseJavaScript(content: string, filePath: string, isTypeScript: boolean): Promise<DetectedFeature[]> {
261
+ const features: DetectedFeature[] = [];
262
+
263
+ try {
264
+ const ast = parseBabel(content, {
265
+ sourceType: 'module',
266
+ plugins: [
267
+ 'typescript' as any,
268
+ 'decorators-legacy' as any,
269
+ 'classProperties' as any,
270
+ 'objectRestSpread' as any,
271
+ 'asyncGenerators' as any,
272
+ 'functionBind' as any,
273
+ 'exportDefaultFrom' as any,
274
+ 'exportNamespaceFrom' as any,
275
+ 'dynamicImport' as any,
276
+ 'nullishCoalescingOperator',
277
+ 'optionalChaining',
278
+ 'topLevelAwait',
279
+ 'privateIn',
280
+ 'importMeta'
281
+ ].filter(plugin => isTypeScript || plugin !== 'typescript')
282
+ });
283
+
284
+ traverse(ast, {
285
+ // Extract Web API member expressions
286
+ MemberExpression: (path: any) => {
287
+ const feature = this.extractWebAPIFeature(path.node, content);
288
+ if (feature) {
289
+ features.push({ ...feature, file: filePath });
290
+ }
291
+ },
292
+
293
+ // Extract Web API function calls
294
+ CallExpression: (path: any) => {
295
+ const feature = this.extractWebAPICall(path.node, content);
296
+ if (feature) {
297
+ features.push({ ...feature, file: filePath });
298
+ }
299
+ },
300
+
301
+ // Modern JavaScript syntax features
302
+ OptionalMemberExpression: (path: any) => {
303
+ features.push({
304
+ feature: 'optional-chaining',
305
+ type: 'js',
306
+ context: this.getContext(content, path.node.loc?.start.line || 0),
307
+ line: path.node.loc?.start.line || 0,
308
+ column: path.node.loc?.start.column || 0,
309
+ file: filePath
310
+ });
311
+ },
312
+
313
+ OptionalCallExpression: (path: any) => {
314
+ features.push({
315
+ feature: 'optional-chaining',
316
+ type: 'js',
317
+ context: this.getContext(content, path.node.loc?.start.line || 0),
318
+ line: path.node.loc?.start.line || 0,
319
+ column: path.node.loc?.start.column || 0,
320
+ file: filePath
321
+ });
322
+ },
323
+
324
+ // Nullish coalescing
325
+ LogicalExpression: (path: any) => {
326
+ if (path.node.operator === '??') {
327
+ features.push({
328
+ feature: 'nullish-coalescing',
329
+ type: 'js',
330
+ context: this.getContext(content, path.node.loc?.start.line || 0),
331
+ line: path.node.loc?.start.line || 0,
332
+ column: path.node.loc?.start.column || 0,
333
+ file: filePath
334
+ });
335
+ }
336
+ },
337
+
338
+ // Private class fields
339
+ ClassPrivateProperty: (path: any) => {
340
+ features.push({
341
+ feature: 'private-fields',
342
+ type: 'js',
343
+ context: this.getContext(content, path.node.loc?.start.line || 0),
344
+ line: path.node.loc?.start.line || 0,
345
+ column: path.node.loc?.start.column || 0,
346
+ file: filePath
347
+ });
348
+ },
349
+
350
+ ClassPrivateMethod: (path: any) => {
351
+ features.push({
352
+ feature: 'private-methods',
353
+ type: 'js',
354
+ context: this.getContext(content, path.node.loc?.start.line || 0),
355
+ line: path.node.loc?.start.line || 0,
356
+ column: path.node.loc?.start.column || 0,
357
+ file: filePath
358
+ });
359
+ },
360
+
361
+ // Top-level await
362
+ AwaitExpression: (path: any) => {
363
+ if (this.isTopLevelAwait(path)) {
364
+ features.push({
365
+ feature: 'top-level-await',
366
+ type: 'js',
367
+ context: this.getContext(content, path.node.loc?.start.line || 0),
368
+ line: path.node.loc?.start.line || 0,
369
+ column: path.node.loc?.start.column || 0,
370
+ file: filePath
371
+ });
372
+ }
373
+ },
374
+
375
+ // Dynamic imports
376
+ Import: (path: any) => {
377
+ features.push({
378
+ feature: 'dynamic-import',
379
+ type: 'js',
380
+ context: this.getContext(content, path.node.loc?.start.line || 0),
381
+ line: path.node.loc?.start.line || 0,
382
+ column: path.node.loc?.start.column || 0,
383
+ file: filePath
384
+ });
385
+ },
386
+
387
+ // BigInt literals
388
+ BigIntLiteral: (path: any) => {
389
+ features.push({
390
+ feature: 'bigint',
391
+ type: 'js',
392
+ context: this.getContext(content, path.node.loc?.start.line || 0),
393
+ line: path.node.loc?.start.line || 0,
394
+ column: path.node.loc?.start.column || 0,
395
+ file: filePath
396
+ });
397
+ },
398
+
399
+ // Numeric separators
400
+ NumericLiteral: (path: any) => {
401
+ if ((path.node.extra as any)?.raw?.includes('_')) {
402
+ features.push({
403
+ feature: 'numeric-separators',
404
+ type: 'js',
405
+ context: this.getContext(content, path.node.loc?.start.line || 0),
406
+ line: path.node.loc?.start.line || 0,
407
+ column: path.node.loc?.start.column || 0,
408
+ file: filePath
409
+ });
410
+ }
411
+ }
412
+ });
413
+
414
+ } catch (error) {
415
+ console.warn(`Warning: Could not parse JavaScript file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
416
+ }
417
+
418
+ return features;
419
+ }
420
+
421
+ private async parseCSS(content: string, filePath: string): Promise<DetectedFeature[]> {
422
+ const features: DetectedFeature[] = [];
423
+
424
+ try {
425
+ const root = postcss.parse(content);
426
+
427
+ // Extract CSS properties
428
+ root.walkDecls((decl: any) => {
429
+ if (this.CSS_PROPERTIES.has(decl.prop) || decl.prop.startsWith('--')) {
430
+ features.push({
431
+ feature: decl.prop,
432
+ type: 'css',
433
+ context: `${decl.prop}: ${decl.value}`,
434
+ line: decl.source?.start?.line || 0,
435
+ column: decl.source?.start?.column || 0,
436
+ file: filePath
437
+ });
438
+ }
439
+
440
+ // Check for CSS functions in values
441
+ if (decl.value.includes('var(') || decl.value.includes('calc(') ||
442
+ decl.value.includes('clamp(') || decl.value.includes('min(') ||
443
+ decl.value.includes('max(')) {
444
+ const func = this.extractCSSFunction(decl.value);
445
+ if (func) {
446
+ features.push({
447
+ feature: func,
448
+ type: 'css',
449
+ context: `${decl.prop}: ${decl.value}`,
450
+ line: decl.source?.start?.line || 0,
451
+ column: decl.source?.start?.column || 0,
452
+ file: filePath
453
+ });
454
+ }
455
+ }
456
+ });
457
+
458
+ // Extract CSS selectors
459
+ root.walkRules((rule: any) => {
460
+ this.CSS_SELECTORS.forEach(selector => {
461
+ if (rule.selector.includes(selector)) {
462
+ features.push({
463
+ feature: selector.replace('(', '()'),
464
+ type: 'css',
465
+ context: rule.selector,
466
+ line: rule.source?.start?.line || 0,
467
+ column: rule.source?.start?.column || 0,
468
+ file: filePath
469
+ });
470
+ }
471
+ });
472
+ });
473
+
474
+ // Extract at-rules
475
+ root.walkAtRules((atRule: any) => {
476
+ const atRuleName = `@${atRule.name}`;
477
+ features.push({
478
+ feature: atRuleName,
479
+ type: 'css',
480
+ context: `${atRuleName} ${atRule.params}`,
481
+ line: atRule.source?.start?.line || 0,
482
+ column: atRule.source?.start?.column || 0,
483
+ file: filePath
484
+ });
485
+ });
486
+
487
+ } catch (error) {
488
+ console.warn(`Warning: Could not parse CSS file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
489
+ }
490
+
491
+ return features;
492
+ }
493
+
494
+ private async parseHTML(content: string, filePath: string): Promise<DetectedFeature[]> {
495
+ const features: DetectedFeature[] = [];
496
+ const lines = content.split('\n');
497
+
498
+ // Parse HTML elements and attributes
499
+ const elementRegex = /<(\w+)([^>]*)>/g;
500
+
501
+ lines.forEach((line, index) => {
502
+ let match: RegExpExecArray | null;
503
+ while ((match = elementRegex.exec(line)) !== null) {
504
+ const tagName = match[1];
505
+ const attributes = match[2];
506
+
507
+ // Check for modern HTML elements
508
+ if (tagName && this.HTML_ELEMENTS.has(tagName)) {
509
+ features.push({
510
+ feature: tagName!,
511
+ type: 'html',
512
+ context: line.trim(),
513
+ line: index + 1,
514
+ column: match.index,
515
+ file: filePath
516
+ });
517
+ }
518
+
519
+ // Check for modern HTML attributes
520
+ if (attributes) {
521
+ const modernAttrs = this.extractHTMLAttributes(attributes);
522
+ modernAttrs.forEach(attr => {
523
+ features.push({
524
+ feature: attr,
525
+ type: 'html',
526
+ context: line.trim(),
527
+ line: index + 1,
528
+ column: match?.index || 0,
529
+ file: filePath
530
+ });
531
+ });
532
+ }
533
+ }
534
+ });
535
+
536
+ // Extract inline CSS and JavaScript
537
+ const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
538
+ const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
539
+
540
+ // Parse inline styles
541
+ let styleMatch;
542
+ while ((styleMatch = styleRegex.exec(content)) !== null) {
543
+ const cssContent = styleMatch[1];
544
+ const cssFeatures = cssContent ? await this.parseInlineCSS(cssContent, filePath) : [];
545
+ features.push(...cssFeatures);
546
+ }
547
+
548
+ // Parse inline scripts
549
+ let scriptMatch;
550
+ while ((scriptMatch = scriptRegex.exec(content)) !== null) {
551
+ const jsContent = scriptMatch[1];
552
+ const jsFeatures = jsContent ? await this.parseInlineJS(jsContent, filePath) : [];
553
+ features.push(...jsFeatures);
554
+ }
555
+
556
+ return features;
557
+ }
558
+
559
+ private async parseInlineCSS(cssContent: string, filePath: string): Promise<DetectedFeature[]> {
560
+ try {
561
+ return await this.parseCSS(cssContent, filePath);
562
+ } catch (error) {
563
+ console.warn(`Warning: Could not parse inline CSS in ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
564
+ return [];
565
+ }
566
+ }
567
+
568
+ private async parseInlineJS(jsContent: string, filePath: string): Promise<DetectedFeature[]> {
569
+ try {
570
+ return await this.parseJavaScript(jsContent, filePath, false);
571
+ } catch (error) {
572
+ console.warn(`Warning: Could not parse inline JavaScript in ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
573
+ return [];
574
+ }
575
+ }
576
+
577
+ private extractWebAPIFeature(node: t.MemberExpression, content: string): DetectedFeature | null {
578
+ const apiName = this.getMemberExpressionName(node);
579
+
580
+ if (this.WEB_PLATFORM_APIS.has(apiName)) {
581
+ return {
582
+ feature: apiName,
583
+ type: 'js',
584
+ context: this.getContext(content, node.loc?.start.line || 0),
585
+ line: node.loc?.start.line || 0,
586
+ column: node.loc?.start.column || 0
587
+ };
588
+ }
589
+
590
+ return null;
591
+ }
592
+
593
+ private extractWebAPICall(node: t.CallExpression, content: string): DetectedFeature | null {
594
+ let apiName = '';
595
+
596
+ if (t.isIdentifier(node.callee)) {
597
+ apiName = node.callee.name;
598
+ } else if (t.isMemberExpression(node.callee)) {
599
+ apiName = this.getMemberExpressionName(node.callee);
600
+ }
601
+
602
+ if (this.WEB_PLATFORM_APIS.has(apiName)) {
603
+ return {
604
+ feature: apiName,
605
+ type: 'js',
606
+ context: this.getContext(content, node.loc?.start.line || 0),
607
+ line: node.loc?.start.line || 0,
608
+ column: node.loc?.start.column || 0
609
+ };
610
+ }
611
+
612
+ return null;
613
+ }
614
+
615
+ private extractCSSFunction(value: string): string | null {
616
+ const functions = ['var(', 'calc(', 'clamp(', 'min(', 'max(', 'minmax('];
617
+ for (const func of functions) {
618
+ if (value.includes(func)) {
619
+ return func.replace('(', '()');
620
+ }
621
+ }
622
+ return null;
623
+ }
624
+
625
+ private extractHTMLAttributes(attributes: string): string[] {
626
+ const modernAttrs: string[] = [];
627
+
628
+ this.HTML_ATTRIBUTES.forEach(attr => {
629
+ const attrRegex = new RegExp(`\\b${attr}\\b`, 'i');
630
+ if (attrRegex.test(attributes)) {
631
+ modernAttrs.push(attr);
632
+ }
633
+ });
634
+
635
+ return modernAttrs;
636
+ }
637
+
638
+ private getMemberExpressionName(node: t.MemberExpression): string {
639
+ const parts: string[] = [];
640
+
641
+ const traverse = (n: t.Expression): void => {
642
+ if (t.isIdentifier(n)) {
643
+ parts.unshift(n.name);
644
+ } else if (t.isMemberExpression(n)) {
645
+ if (t.isIdentifier(n.property)) {
646
+ parts.unshift(n.property.name);
647
+ }
648
+ traverse(n.object);
649
+ }
650
+ };
651
+
652
+ traverse(node);
653
+ return parts.join('.');
654
+ }
655
+
656
+ private isTopLevelAwait(path: any): boolean {
657
+ let parent = path.parent;
658
+ while (parent) {
659
+ if (t.isFunction(parent) || t.isArrowFunctionExpression(parent)) {
660
+ return false;
661
+ }
662
+ parent = path.parentPath?.parent;
663
+ }
664
+ return true;
665
+ }
666
+
667
+ private getFileExtension(filePath: string): string {
668
+ const match = filePath.match(/\.[^.]+$/);
669
+ return match ? match[0] : '';
670
+ }
671
+
672
+ private getContext(content: string, line: number): string {
673
+ const lines = content.split('\n');
674
+ const targetLine = lines[line - 1] || '';
675
+ return targetLine.trim();
676
+ }
677
+
678
+ getSupportedExtensions(): string[] {
679
+ return ['.js', '.ts', '.html', '.css'];
680
+ }
681
+
682
+ getName(): string {
683
+ return 'VanillaParser';
684
+ }
685
+ }