Class: Mbeditor::JsProgramService
- Inherits:
-
Object
- Object
- Mbeditor::JsProgramService
- Defined in:
- app/services/mbeditor/js_program_service.rb
Overview
Enumerates the workspace's own JavaScript source and hands it to the editor so Monaco's TypeScript worker can build a real program from it.
This is the file-based replacement for declaring every discovered name as
ambient any (JsGlobalsService). Under Sprockets a JS file with no
import/export is a script, so its top-level declarations land in the
global scope — which is exactly TypeScript's own model for script files.
Giving the worker the sources instead of a name list yields real inferred
types, member completions, and argument-count checking, and it still
reports "Cannot find name" for genuinely unknown identifiers.
What it deliberately does NOT solve: UMD-wrapped libraries. React, lodash
and axios all assign their global inside a closure
(factory(global.React = {})), which TypeScript cannot follow statically —
loading their source produces no global at all. Those stay on hand-written
declarations (the React mini-UMD stub) or on JsGlobalsService's ambient
names, which is why that service is still here.
No truncation: a workspace that exceeds any limit reports what it skipped and why, rather than silently returning a partial program.
Constant Summary collapse
- SOURCE_EXT =
/\.(js|jsx|ts|tsx)\z/i- MINIFIED_NAME =
Minified bundles cost parse time and yield nothing useful — their globals are one-letter names inside a closure. Matched by convention, then by shape for bundles whose filename doesn't say so.
/[.\-]min\.(js|jsx|ts|tsx)\z/i- MAX_LINE_LENGTH =
2_000- MAX_FILE_BYTES =
A single source file this large is a bundle or generated output, not something a person edits.
1024 * 1024
- CACHE_TTL =
seconds
10
Class Method Summary collapse
- .call(workspace_root) ⇒ Object
-
.file(workspace_root, relative_path) ⇒ Object
Content for a single workspace-relative path, for incremental refresh after a file changes.
- .invalidate(workspace_root) ⇒ Object
Class Method Details
.call(workspace_root) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'app/services/mbeditor/js_program_service.rb', line 43 def call(workspace_root) root = File.(workspace_root.to_s) now = monotonic MUTEX.synchronize do entry = (@cache ||= {})[root] return entry[:data] if entry && (now - entry[:ts]) < CACHE_TTL end data = compute(root) MUTEX.synchronize { (@cache ||= {})[root] = { ts: monotonic, data: data } } data end |
.file(workspace_root, relative_path) ⇒ Object
Content for a single workspace-relative path, for incremental refresh after a file changes. Returns nil when the path isn't program material.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'app/services/mbeditor/js_program_service.rb', line 58 def file(workspace_root, relative_path) root = File.(workspace_root.to_s) rel = relative_path.to_s.delete_prefix("/") return nil unless rel.match?(SOURCE_EXT) return nil if rel.match?(MINIFIED_NAME) return nil if matcher(root).excluded?(rel) abs = File.(File.join(root, rel)) return nil unless abs == File.join(root, rel) # no traversal out of the workspace return nil unless File.file?(abs) && !File.symlink?(abs) content = read_source(abs) content && { path: rel, content: content } end |
.invalidate(workspace_root) ⇒ Object
73 74 75 |
# File 'app/services/mbeditor/js_program_service.rb', line 73 def invalidate(workspace_root) MUTEX.synchronize { (@cache ||= {}).delete(File.(workspace_root.to_s)) } end |