Add lsp command
MODIFY
packages/@romejs/cli-flags/Parser.ts
packages/@romejs/cli-flags/Parser.ts
@@ -155,6 +155,15 @@ export default class Parser<T> {
continue;
}
+ // Allow for arguments to be passed as --foo=bar
+ const equalsIndex = name.indexOf('=');
+ if (equalsIndex !== -1) {
+ const cleanName = name.slice(0, equalsIndex);
+ const value = name.slice(equalsIndex + 1);
+ this.flags.set(cleanName, value);
+ continue;
+ }
+
// If the next argument is a flag or we're at the end of the args then just set it to `true`
if (rawArgs.length === 0 || this.looksLikeFlag(rawArgs[0])) {
this.flags.set(name, true);

MODIFY
packages/@romejs/core/client/Client.ts
packages/@romejs/core/client/Client.ts
@@ -49,7 +49,7 @@ type ClientOptions = {
stdout?: stream.Writable;
stderr?: stream.Writable;
stdin?: NodeJS.ReadStream;
- flags: Partial<Omit<ClientFlags, 'clientName'>> & {clientName: string};
+ flags: Partial<Omit<ClientFlags, 'clientName'>>;
};
export type ClientProfileOptions = {
@@ -119,6 +119,14 @@ export default class Client {
requestResponseEvent: Event<ClientRequestResponseResult, void>;
endEvent: Event<void, void>;
+ setClientName(name: string) {
+ if (this.bridgeStatus !== undefined) {
+ throw new Error('Already connected to bridge. Cannot change client name');
+ }
+
+ this.flags.clientName = name;
+ }
+
getBridgeJSONFlags(): MasterBridgeJSONFlags {
return {
...this.flags,
@@ -523,10 +531,18 @@ export default class Client {
proc.kill();
}
- console.log('ughhh???');
return undefined;
}
+ async connectToDaemon(): Promise<undefined | MasterBridge> {
+ const bridge = await this.tryConnectToExistingDaemon();
+ if (bridge !== undefined) {
+ return bridge;
+ }
+
+ return await this.startDaemon();
+ }
+
async tryConnectToNewDaemon(): Promise<undefined | MasterBridge> {
const bridge = await this.tryConnectToExistingDaemon();
if (bridge !== undefined) {

MODIFY
packages/@romejs/core/client/commands.ts
packages/@romejs/core/client/commands.ts
@@ -253,3 +253,37 @@ localCommands.set('status', {
}
},
});
+
+localCommands.set('lsp', {
+ description: 'connect to an lsp',
+ category: commandCategories.PROJECT_MANAGEMENT,
+ defineFlags(consumer) {
+ // vscode-languageclient adds these on
+ consumer.get('stdio').asBooleanOrVoid();
+ consumer.get('clientProcessId').asStringOrVoid();
+ return {};
+ },
+
+ async callback(req: ClientRequest) {
+ // Better API
+ req.client.setClientName('lsp');
+ req.client.flags.silent = true;
+
+ const stdin = req.client.reporter.getStdin();
+ req.client.reporter.teardown();
+
+ const bridge = await req.client.findOrStartMaster();
+
+ bridge.lspBuffer.subscribe((chunk) => {
+ req.client.derivedReporterStreams.stdout.write(chunk);
+ });
+
+ stdin.on('data', (chunk) => {
+ bridge.lspBuffer.call(chunk.toString());
+ });
+
+ await new Promise(() => {});
+
+ return true;
+ },
+});

MODIFY
packages/@romejs/core/common/bridges/MasterBridge.ts
packages/@romejs/core/common/bridges/MasterBridge.ts
@@ -154,4 +154,9 @@ export default class MasterBridge extends Bridge {
name: 'profile.stopWorker',
direction: 'server<-client',
});
+
+ lspBuffer = this.createEvent<string, void>({
+ name: 'lspBuffer',
+ direction: 'server<->client',
+ });
}

MODIFY
packages/@romejs/core/common/fileHandlers.ts
packages/@romejs/core/common/fileHandlers.ts
@@ -138,7 +138,8 @@ const textHandler: ExtensionHandler = {
},
async toJavaScript({file}) {
- const src = await readFileText(file.real);
+ const src = file.sourceText === undefined
+ ? await readFileText(file.real) : file.sourceText;
const serial = JSON.stringify(src);
return {
sourceText: `export default ${serial};`,
@@ -217,7 +218,8 @@ const jsonHandler: ExtensionHandler = {
},
async toJavaScript({file}) {
- const src = await readFileText(file.real);
+ const src = file.sourceText === undefined
+ ? await readFileText(file.real) : file.sourceText;
// Parse the JSON to make sure it's valid
const obj = parseJSON({
@@ -299,7 +301,8 @@ function buildJSHandler(
async toJavaScript({file}) {
return {
- sourceText: await readFileText(file.real),
+ sourceText: file.sourceText === undefined
+ ? await readFileText(file.real) : file.sourceText,
generated: false,
};
},

MODIFY
packages/@romejs/core/common/types/files.ts
packages/@romejs/core/common/types/files.ts
@@ -13,6 +13,7 @@ export type FileReference = {
uid: string;
real: AbsoluteFilePath;
remote: boolean;
+ sourceText?: string;
};
export type JSONFileReference = Omit<FileReference, 'real'> & {real: string};

MODIFY
packages/@romejs/core/master/Master.ts
packages/@romejs/core/master/Master.ts
@@ -59,6 +59,7 @@ import {
createUnknownFilePath,
} from '@romejs/path';
import {Dict} from '@romejs/typescript-helpers';
+import {LSPConnection} from './lsp/index';
const STDOUT_MAX_CHUNK_LENGTH = 100_000;
@@ -375,11 +376,19 @@ export default class Master {
return;
}
- await this.clientStartEvent.callOptional(client);
+ const connection = new LSPConnection(this, client, (chunk) => {
+ bridge.lspBuffer.send(chunk);
+ });
+
+ bridge.lspBuffer.subscribe((chunk) => {
+ connection.append(chunk);
+ });
bridge.query.subscribe(async (request) => {
return await this.handleRequest(client, request);
});
+
+ await this.clientStartEvent.callOptional(client);
}
async createClient(bridge: MasterBridge): Promise<MasterClient> {
@@ -474,8 +483,10 @@ export default class Master {
});
// Add reporter to connected set, important logs may be output to these
+ if (!flags.silent) {
this.connectedReporters.addStream(errStream);
this.connectedReporters.addStream(outStream);
+ }
const client: MasterClient = {
id: this.clientIdCounter++,
@@ -584,7 +595,6 @@ export default class Master {
});
}
- // Create master request wrapper which is just a bucket of objects
const req = new MasterRequest({
client,
query,

MODIFY
packages/@romejs/core/master/MasterRequest.ts
packages/@romejs/core/master/MasterRequest.ts
@@ -430,7 +430,7 @@ export default class MasterRequest {
}
// Hint if all files were ignored
- if (configCategory !== undefined) {
+ if (configCategory !== undefined && !ignoreProjectIgnore) {
const {paths: withoutIgnore} = await this.getFilesFromArgs({
...opts,
ignoreProjectIgnore: true,

MODIFY
packages/@romejs/core/master/commands/format.ts
packages/@romejs/core/master/commands/format.ts
import {MasterRequest} from '@romejs/core';
import {createMasterCommand} from '../../commands';
import {commandCategories} from '../../commands';
-import {DiagnosticsProcessor, descriptions} from '@romejs/diagnostics';
-import {FORMATTABLE_EXTENSIONS} from '@romejs/core/common/fileHandlers';
-import {Consumer} from '@romejs/consume';
-
-type Flags = {write: boolean};
+import {createUnknownFilePath} from '@romejs/path';
export default createMasterCommand({
category: commandCategories.INTERNAL,
description: 'TODO',
- defineFlags(consumer: Consumer): Flags {
- return {
- write: consumer.get('write').asBoolean(false),
- };
- },
-
- async default(req: MasterRequest, flags: Flags): Promise<void> {
- const {reporter} = req;
+ async default(req: MasterRequest): Promise<undefined | string> {
+ const {reporter, master} = req;
+ const {args} = req.query;
+ req.expectArgumentLength(1);
- const {paths} = await req.getFilesFromArgs({
- noun: 'formatting',
- verb: 'linting',
- configCategory: 'format',
- extensions: FORMATTABLE_EXTENSIONS,
- disabledDiagnosticCategory: 'format/disabled',
- });
-
- if (paths.size === 0) {
- reporter.warn('No files formatted');
- return;
- }
+ const filename = await master.resolver.resolveEntryAssertPath({
+ ...req.getResolverOptionsFromFlags(),
+ source: createUnknownFilePath(args[0]),
+ }, {location: req.getDiagnosticPointerFromFlags({type: 'arg', key: 0})});
- const pathsByWorker = await req.master.fileAllocator.groupPathsByWorker(
- paths,
- );
-
- const progress = reporter.progress();
- progress.setTotal(paths.size);
- progress.setTitle('Formatting');
-
- const diagnosticsProcessor = new DiagnosticsProcessor({});
-
- // TODO probably add the same logic in CompilerLinter if the project config disables formatting
- await Promise.all(pathsByWorker.map(async (paths) => {
- for (const path of paths) {
- progress.setText(`<filelink target="${path.join()}" />`);
- progress.tick();
-
- const res = await req.requestWorkerFormat(path);
+ const res = await req.requestWorkerFormat(filename);
if (res === undefined) {
- continue;
- }
-
- if (res.diagnostics.length > 0) {
- diagnosticsProcessor.addDiagnostics(res.diagnostics);
- continue;
- }
-
- if (!flags.write && res.formatted !== res.original) {
- diagnosticsProcessor.addDiagnostic({
- description: descriptions.LINT.PENDING_FIXES(
- res.original,
- res.formatted,
- ),
- location: {
- filename: path.join(),
- },
- });
- } else {
- //await writeFile(path, res.formatted);
- }
- }
- }));
-
- diagnosticsProcessor.maybeThrowDiagnosticsError();
-
- if (flags.write) {
- reporter.success(
- `<number emphasis>${paths.size}</number> files formatted successfully`,
- );
+ reporter.error('No formatter for this file');
+ return undefined;
} else {
- reporter.success(
- `<number emphasis>${paths.size}</number> files formatted correctly`,
- );
+ reporter.writeAll(res.formatted);
+ return res.formatted;
}
},
});

ADD
packages/@romejs/core/master/lsp/index.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {consumeUnknown} from '@romejs/consume';
+import {
+ LSPNotificationMessage,
+ LSPRequestMessage,
+ LSPResponseMessage,
+ LSPDiagnostic,
+ LSPPosition,
+} from './types';
+import Master, {MasterClient} from '../Master';
+import {createAbsoluteFilePath} from '@romejs/path';
+import {Diagnostics} from '@romejs/diagnostics';
+import {Position, UNKNOWN_POSITION} from '@romejs/parser-core';
+import {coerce1to0, get0} from '@romejs/ob1';
+import {stripMarkupTags} from '@romejs/string-markup';
+
+type Status = 'IDLE' | 'WAITING_FOR_HEADERS_END' | 'WAITING_FOR_RESPONSE_END';
+
+type Headers = {
+ length: number;
+ extra: Map<string, string>;
+};
+
+const HEADERS_END = '\r\n\r\n';
+
+function parseHeaders(buffer: string): Headers {
+ const headers: Map<string, string> = new Map();
+
+ for (const line of buffer.split('\n')) {
+ const clean = line.trim();
+ const match = clean.match(/^(.*?): (.*?)$/);
+ if (match == null) {
+ throw new Error(`Invalid header: ${clean}`);
+ }
+
+ const [, key, value] = match;
+ headers.set(key.toLowerCase(), value);
+ }
+
+ const length = headers.get('content-length');
+ if (length === undefined) {
+ throw new Error('Expected Content-Length');
+ }
+ headers.delete('content-length');
+
+ return {
+ length: Number(length),
+ extra: headers,
+ };
+}
+
+function convertPositionToLSP(pos: undefined | Position): LSPPosition {
+ if (pos === undefined) {
+ return {
+ line: 0,
+ character: 0,
+ };
+ } else {
+ return {
+ line: get0(coerce1to0(pos.line)),
+ character: get0(pos.column),
+ };
+ }
+}
+
+function convertDiagnosticsToLSP(
+ diagnostics: Diagnostics,
+): Array<LSPDiagnostic> {
+ const lspDiagnostics: Array<LSPDiagnostic> = [];
+
+ for (const {description, location} of diagnostics) {
+ lspDiagnostics.push({
+ severity: 1,
+ range: {
+ start: convertPositionToLSP(location.start),
+ end: convertPositionToLSP(location.end),
+ },
+ message: stripMarkupTags(description.message.value),
+ code: description.category,
+ source: 'rome',
+ });
+ }
+
+ return lspDiagnostics;
+}
+
+type Writer = (chunk: string) => void;
+
+export class LSPConnection {
+ constructor(master: Master, client: MasterClient, write: Writer) {
+ this.status = 'IDLE';
+ this.buffer = '';
+ this.nextHeaders = undefined;
+ this._write = write;
+ this.master = master;
+ this.client = client;
+ }
+
+ client: MasterClient;
+ master: Master;
+ _write: Writer;
+ nextHeaders: undefined | Headers;
+ status: Status;
+ buffer: string;
+
+ write(res: unknown) {
+ const json = JSON.stringify(res);
+ const out = `Content-Length: ${String(json.length)}${HEADERS_END}${json}`;
+ this._write(out);
+ }
+
+ getNextHeaders(): Headers {
+ const {nextHeaders} = this;
+ if (nextHeaders === undefined) {
+ throw new Error('Expected headers due to our status');
+ }
+ return nextHeaders;
+ }
+
+ async handleRequest(req: LSPRequestMessage): Promise<unknown> {
+ const params = consumeUnknown(req.params, 'lsp/parse');
+
+ switch (req.method) {
+ case 'initialize':
+ await this.master.projectManager.assertProject(
+ createAbsoluteFilePath(params.get('rootUri').asString()),
+ );
+ return {
+ capabilities: {
+ textDocumentSync: 1,
+ documentFormattingProvider: true,
+ },
+ serverInfo: {
+ name: 'rome',
+ },
+ };
+
+ case 'textDocument/formatting':
+ const uri = params
+ .get('textDocument')
+ .get('uri')
+ .asString();
+ const res = await this.master.handleRequest(this.client, {
+ command: 'format',
+ args: [uri],
+ silent: true,
+ });
+ }
+
+ return {};
+ }
+
+ async handleNotification(notif: LSPNotificationMessage): Promise<void> {
+ const params = consumeUnknown(notif.params, 'lsp/parse');
+
+ switch (notif.method) {
+ case 'textDocument/didOpen':
+ const uri = params
+ .get('textDocument')
+ .get('uri')
+ .asString();
+ const res = await this.master.handleRequest(this.client, {
+ command: 'lint',
+ args: [uri],
+ silent: true,
+ });
+ if (res.type === 'DIAGNOSTICS') {
+ this.write({
+ method: 'textDocument/publishDiagnostics',
+ params: {
+ uri,
+ diagnostics: convertDiagnosticsToLSP(res.diagnostics),
+ },
+ });
+ }
+ }
+ }
+
+ async onMessage(headers: Headers, content: string) {
+ const data = JSON.parse(content);
+ const consumer = consumeUnknown(data, 'lsp/parse');
+
+ const notif: LSPNotificationMessage = {
+ method: consumer.get('method').asString(),
+ params: consumer.get('params').asUnknown(),
+ };
+
+ if (consumer.has('id')) {
+ const req: LSPRequestMessage = {
+ // id can also be a string?
+ id: consumer.get('id').asNumber(),
+ ...notif,
+ };
+
+ try {
+ const res: LSPResponseMessage = {
+ id: req.id,
+ result: await this.handleRequest(req),
+ };
+ this.write(res);
+ } catch (err) {
+ const res: LSPResponseMessage = {
+ id: req.id,
+ error: {
+ code: -32603,
+ message: err.message,
+ },
+ };
+ this.write(res);
+ }
+ } else {
+ await this.handleNotification(notif);
+ }
+ }
+
+ process() {
+ switch (this.status) {
+ case 'IDLE':
+ if (this.buffer.length > 0) {
+ this.status = 'WAITING_FOR_HEADERS_END';
+ this.process();
+ }
+ break;
+
+ case 'WAITING_FOR_HEADERS_END':
+ const endIndex = this.buffer.indexOf(HEADERS_END);
+ if (endIndex !== -1) {
+ // Parse headers
+ const rawHeaders = this.buffer.slice(0, endIndex);
+ this.nextHeaders = parseHeaders(rawHeaders);
+
+ // Process rest of the buffer
+ this.status = 'WAITING_FOR_RESPONSE_END';
+ this.buffer = this.buffer.slice(endIndex + HEADERS_END.length);
+ this.process();
+ }
+ break;
+
+ case 'WAITING_FOR_RESPONSE_END':
+ const headers = this.getNextHeaders();
+ if (this.buffer.length >= headers.length) {
+ const content = this.buffer.slice(0, headers.length);
+ this.onMessage(headers, content);
+
+ // Reset headers and trim content
+ this.nextHeaders = undefined;
+ this.buffer = this.buffer.slice(headers.length);
+
+ // Process rest of the buffer
+ this.status = 'IDLE';
+ this.process();
+ }
+ break;
+ }
+ }
+
+ append(data: string) {
+ this.buffer += data;
+ this.process();
+ }
+}

ADD
packages/@romejs/core/master/lsp/types.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+export type LSPRequestMessage = {
+ /**
+ * The request id.
+ */
+ id: number | string;
+
+ /**
+ * The method to be invoked.
+ */
+ method: string;
+
+ /**
+ * The method's params.
+ */
+ params?: unknown;
+};
+
+export type LSPResponseMessage = {
+ /**
+ * The request id.
+ */
+ id: number | string | null;
+
+ /**
+ * The result of a request. This member is REQUIRED on success.
+ * This member MUST NOT exist if there was an error invoking the method.
+ */
+ result?: unknown;
+
+ /**
+ * The error object in case a request fails.
+ */
+ error?: LSPResponseError;
+};
+
+export type LSPResponseError = {
+ /**
+ * A number indicating the error type that occurred.
+ */
+ code: LSPErrorCodes[keyof LSPErrorCodes];
+
+ /**
+ * A string providing a short description of the error.
+ */
+ message: string;
+
+ /**
+ * A primitive or structured value that contains additional
+ * information about the error. Can be omitted.
+ */
+ data?: unknown;
+};
+
+export type LSPNotificationMessage = {
+ /**
+ * The method to be invoked.
+ */
+ method: string;
+
+ /**
+ * The notification's params.
+ */
+ params?: unknown;
+};
+
+export type LSPErrorCodes = {
+ // Defined by JSON RPC
+ ParseError: -32700;
+ InvalidRequest: -32600;
+ MethodNotFound: -32601;
+ InvalidParams: -32602;
+ InternalError: -32603;
+ serverErrorStart: -32099;
+ serverErrorEnd: -32000;
+ ServerNotInitialized: -32002;
+ UnknownErrorCode: -32001;
+
+ // Defined by the protocol.
+ RequestCancelled: -32800;
+ ContentModified: -32801;
+};
+
+export type LSPPosition = {
+ /**
+ * Line position in a document (zero-based).
+ */
+ line: number;
+
+ /**
+ * Character offset on a line in a document (zero-based). Assuming that the line is
+ * represented as a string, the `character` value represents the gap between the
+ * `character` and `character + 1`.
+ *
+ * If the character value is greater than the line length it defaults back to the
+ * line length.
+ */
+ character: number;
+};
+
+export type LSPRange = {
+ /**
+ * The range's start position.
+ */
+ start: LSPPosition;
+
+ /**
+ * The range's end position.
+ */
+ end: LSPPosition;
+};
+
+export type LSPDocumentUri = string;
+
+export type LSPLocation = {
+ uri: LSPDocumentUri;
+ range: LSPRange;
+};
+
+export type LSPLocationLink = {
+ /**
+ * Span of the origin of this link.
+ *
+ * Used as the underlined span for mouse interaction. Defaults to the word range at
+ * the mouse position.
+ */
+ originSelectionRange?: LSPRange;
+
+ /**
+ * The target resource identifier of this link.
+ */
+ targetUri: LSPDocumentUri;
+
+ /**
+ * The full target range of this link. If the target for example is a symbol then target range is the
+ * range enclosing this symbol not including leading/trailing whitespace but everything else
+ * like comments. This information is typically used to highlight the range in the editor.
+ */
+ targetRange: LSPRange;
+
+ /**
+ * The range that should be selected and revealed when this link is being followed, e.g the name of a function.
+ * Must be contained by the the `targetRange`. See also `DocumentSymbol#range`
+ */
+ targetSelectionRange: LSPRange;
+};
+
+export type LSPDiagnostic = {
+ /**
+ * The range at which the message applies.
+ */
+ range: LSPRange;
+
+ /**
+ * The diagnostic's severity. Can be omitted. If omitted it is up to the
+ * client to interpret diagnostics as error, warning, info or hint.
+ */
+ severity?: LSPDiagnosticSeverity[keyof LSPDiagnosticSeverity];
+
+ /**
+ * The diagnostic's code, which might appear in the user interface.
+ */
+ code?: number | string;
+
+ /**
+ * A human-readable string describing the source of this
+ * diagnostic, e.g. 'typescript' or 'super lint'.
+ */
+ source?: string;
+
+ /**
+ * The diagnostic's message.
+ */
+ message: string;
+
+ /**
+ * Additional metadata about the diagnostic.
+ *
+ * @since 3.15.0
+ */
+ tags?: Array<LSPDiagnosticTag[keyof LSPDiagnosticTag]>;
+
+ /**
+ * An array of related diagnostic information, e.g. when symbol-names within
+ * a scope collide all definitions can be marked via this property.
+ */
+ relatedInformation?: Array<LSPDiagnosticRelatedInformation>;
+};
+
+export type LSPDiagnosticSeverity = {
+ /**
+ * Reports an error.
+ */
+ Error: 1;
+ /**
+ * Reports a warning.
+ */
+ Warning: 2;
+ /**
+ * Reports an information.
+ */
+ Information: 3;
+ /**
+ * Reports a hint.
+ */
+ Hint: 4;
+};
+
+/**
+ * The diagnostic tags.
+ *
+ * @since 3.15.0
+ */
+export type LSPDiagnosticTag = {
+ /**
+ * Unused or unnecessary code.
+ *
+ * Clients are allowed to render diagnostics with this tag faded out instead of having
+ * an error squiggle.
+ */
+ Unnecessary: 1;
+ /**
+ * Deprecated or obsolete code.
+ *
+ * Clients are allowed to rendered diagnostics with this tag strike through.
+ */
+ Deprecated: 2;
+};
+
+/**
+ * Represents a related message and source code location for a diagnostic. This should be
+ * used to point to code locations that cause or are related to a diagnostics, e.g when duplicating
+ * a symbol in a scope.
+ */
+export type LSPDiagnosticRelatedInformation = {
+ /**
+ * The location of this related diagnostic information.
+ */
+ location: LSPLocation;
+
+ /**
+ * The message of this related diagnostic information.
+ */
+ message: string;
+};
+
+export type LSPTextEdit = {
+ /**
+ * The range of the text document to be manipulated. To insert
+ * text into a document create a range where start === end.
+ */
+ range: LSPRange;
+
+ /**
+ * The string to be inserted. For delete operations use an
+ * empty string.
+ */
+ newText: string;
+};

MODIFY
packages/@romejs/core/master/project/ProjectManager.ts
packages/@romejs/core/master/project/ProjectManager.ts
@@ -863,6 +863,7 @@ export default class ProjectManager {
for (const configFilename of ROME_CONFIG_FILENAMES) {
// Check in root
const configPath = dir.append(configFilename);
+
const hasProject = await this.master.memoryFs.existsHard(configPath);
if (hasProject) {
return this.queueAddProject(dir, configPath);

MODIFY
packages/@romejs/core/worker/Worker.ts
packages/@romejs/core/worker/Worker.ts
@@ -319,13 +319,21 @@ export default class Worker {
sourceType = 'module';
}
- const cacheEnabled = opts.cache !== false;
+ const cacheEnabled = opts.cache !== false && ref.sourceText === undefined;
+
if (cacheEnabled) {
// Update the lastAccessed of the ast cache and return it, it will be evicted on
// any file change
const cachedResult: undefined | ParseResult = this.astCache.get(path);
- if (cachedResult && cachedResult.ast.sourceType === sourceType) {
+ if (cachedResult !== undefined) {
+ let useCached = true;
+
+ if (cachedResult.ast.sourceType !== sourceType) {
+ useCached = false;
+ }
+
+ if (useCached) {
this.astCache.set(path, {
...cachedResult,
lastAccessed: Date.now(),
@@ -333,6 +341,7 @@ export default class Worker {
return cachedResult;
}
}
+ }
this.logger.info(`Parsing:`, path);

MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -56,6 +56,7 @@ export type DiagnosticCategory =
| 'lint/undeclaredVariables'
| 'lint/unsafeNegation'
| 'lint/unusedVariables'
+ | 'lsp/parse'
| 'parse/url/query'
| 'parse/url'
| 'parse/js'

MODIFY
packages/@romejs/events/Event.ts
packages/@romejs/events/Event.ts
@@ -58,7 +58,7 @@ export default class Event<Param, Ret = void> {
send(param: Param) {
const {rootSubscription} = this;
if (rootSubscription === undefined) {
- return undefined;
+ return;
}
rootSubscription(param);

MODIFY
packages/@romejs/js-parser/parser.ts
packages/@romejs/js-parser/parser.ts
@@ -390,6 +390,7 @@ const createJSParser = createParser((ParserCore, ParserWithRequiredPath) =>
...opts.description,
},
location: {
+ filename: this.filename,
sourceType: this.sourceType,
mtime: this.mtime,
start,

Add lsp command