diff --git a/language-server/src/clientFileEventListener.ts b/language-server/src/clientFileEventListener.ts index 8c6dbab2..90476cc6 100644 --- a/language-server/src/clientFileEventListener.ts +++ b/language-server/src/clientFileEventListener.ts @@ -6,12 +6,12 @@ import winston from 'winston' import { ConnectionToken } from './connection' import { ProjectFileAdded, ProjectFileChanged, ProjectFileDeleted } from './events' import { FileStore } from './fileStore' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass } from './log' @tsyringe.singleton() export class ClientFileEventListener { private log = winston.createLogger({ - format: winston.format.combine(className(ClientFileEventListener), logFormat), + format: winston.format.combine(withClass(ClientFileEventListener)), transports: [defaultLogger()], }) diff --git a/language-server/src/defManager.ts b/language-server/src/defManager.ts index 48d85e39..bbb0dbf8 100644 --- a/language-server/src/defManager.ts +++ b/language-server/src/defManager.ts @@ -14,12 +14,12 @@ import _ from 'lodash' import { MultiDictionary } from 'typescript-collections' import * as winston from 'winston' import { DocumentWithNodeMap } from './documentWithNodeMap' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass, withVersion } from './log' import { RimWorldVersion } from './RimWorldVersion' export class DefManager { private log = winston.createLogger({ - format: winston.format.combine(className(DefManager), logFormat), + format: winston.format.combine(withClass(DefManager), withVersion(this.version ?? '')), transports: [defaultLogger()], }) diff --git a/language-server/src/events/event.ts b/language-server/src/events/event.ts index 69dca339..ef95f138 100644 --- a/language-server/src/events/event.ts +++ b/language-server/src/events/event.ts @@ -31,6 +31,7 @@ export interface TextRequestResponse { } export interface TypeInfoRequest { + version: string uris: string[] } diff --git a/language-server/src/features/codeLens.ts b/language-server/src/features/codeLens.ts index 7a79414d..f169ffed 100644 --- a/language-server/src/features/codeLens.ts +++ b/language-server/src/features/codeLens.ts @@ -10,7 +10,7 @@ import * as tsyringe from 'tsyringe' import * as lsp from 'vscode-languageserver' import { URI } from 'vscode-uri' import winston from 'winston' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { Project } from '../project' import { ProjectManager } from '../projectManager' import { RangeConverter } from '../utils/rangeConverter' @@ -67,7 +67,7 @@ const resultToCodeLens = (r: Result): lsp.CodeLens => ({ @tsyringe.singleton() export class CodeLens implements Provider { private log = winston.createLogger({ - format: winston.format.combine(className(CodeLens), logFormat), + format: winston.format.combine(withClass(CodeLens)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/commands/defListRequestHandler.ts b/language-server/src/features/commands/defListRequestHandler.ts index b5067ef7..09e4aff2 100644 --- a/language-server/src/features/commands/defListRequestHandler.ts +++ b/language-server/src/features/commands/defListRequestHandler.ts @@ -4,7 +4,7 @@ import * as tsyringe from 'tsyringe' import { Connection } from 'vscode-languageserver' import winston from 'winston' import { DefListRequest, DefListRequestResponse } from '../../events' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { ProjectManager } from '../../projectManager' import { PlainObject } from '../../types/plainObject' import { Provider } from '../provider' @@ -13,7 +13,7 @@ import { ProjectHelper } from '../utils/project' @tsyringe.injectable() export class DefListRequestHandler implements Provider { private log = winston.createLogger({ - format: winston.format.combine(className(DefListRequestHandler), logFormat), + format: winston.format.combine(withClass(DefListRequestHandler)), transports: [defaultLogger()], }) @@ -27,8 +27,11 @@ export class DefListRequestHandler implements Provider { return this.log } - private async onRequest({ version }: DefListRequest): Promise { + private async onRequest({ version }: DefListRequest): Promise { const project = this.projectManager.getProject(version) + if (!project) { + return null + } const marshalledDefs = AsEnumerable(project.defManager.defDatabase.defs()) .Select((def) => this.marshalDef(def)) diff --git a/language-server/src/features/commands/parsedTypeInfoRequest.ts b/language-server/src/features/commands/parsedTypeInfoRequest.ts index afb27a8a..72cd36fc 100644 --- a/language-server/src/features/commands/parsedTypeInfoRequest.ts +++ b/language-server/src/features/commands/parsedTypeInfoRequest.ts @@ -3,7 +3,7 @@ import { Connection, ResponseError } from 'vscode-languageserver' import * as winston from 'winston' import { Logger } from 'winston' import { ParsedTypeInfoRequest, ParsedTypeInfoRequestResponse } from '../../events' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { ProjectManager } from '../../projectManager' import { Provider } from '../provider' import { ProjectHelper } from '../utils/project' @@ -11,7 +11,7 @@ import { ProjectHelper } from '../utils/project' @tsyringe.injectable() export class ParsedTypeInfoRequestHandler implements Provider { private log = winston.createLogger({ - format: winston.format.combine(className(ParsedTypeInfoRequestHandler), logFormat), + format: winston.format.combine(withClass(ParsedTypeInfoRequestHandler)), transports: [defaultLogger()], }) @@ -29,6 +29,9 @@ export class ParsedTypeInfoRequestHandler implements Provider { version, }: ParsedTypeInfoRequest): Promise> { const project = this.projectManager.getProject(version) + if (!project) { + return new ResponseError(1, `Project for version ${version} not found`) + } const [typeInfoMap, error] = await project.getTypeInfo() if (error) { diff --git a/language-server/src/features/decorate.ts b/language-server/src/features/decorate.ts index bb5c4009..2336bb33 100644 --- a/language-server/src/features/decorate.ts +++ b/language-server/src/features/decorate.ts @@ -4,7 +4,7 @@ import * as tsyringe from 'tsyringe' import { Connection } from 'vscode-languageserver' import * as winston from 'winston' import { DocumentTokenRequest, DocumentTokenRequestResponse } from '../events' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { Project } from '../project' import { DocumentToken } from '../types/documentToken' import { RangeConverter } from '../utils/rangeConverter' @@ -17,7 +17,7 @@ import { ProjectHelper } from './utils/project' @tsyringe.injectable() export class DecoProvider implements Provider { private log = winston.createLogger({ - format: winston.format.combine(className(DecoProvider), logFormat), + format: winston.format.combine(withClass(DecoProvider)), transports: [defaultLogger()], }) @@ -34,8 +34,11 @@ export class DecoProvider implements Provider { ) } - private onTokenRequest(p: DocumentTokenRequest): DocumentTokenRequestResponse | null | undefined { - const projects = this.projectHelper.getProjects(p.uri) + private onTokenRequest(p: DocumentTokenRequest): DocumentTokenRequestResponse | null { + const projects = this.projectHelper.getProjects(p.uri).filter((project) => project.state === 'ready') + if (projects.length === 0) { + return { tokens: [], uri: p.uri } + } const tokens: DocumentToken[] = [] @@ -52,9 +55,8 @@ export class DecoProvider implements Provider { return { uri: p.uri, tokens } } - private getTokenFromDoc(project: Project, doc: Document) { + private getTokenFromDoc(project: Project, doc: Document): DocumentToken[] { // traverse nodes and get nodes - const nodes: Node[] = this.getNodesBFS(doc) return nodes.map((node) => this.getTokens(project, node)).flat() diff --git a/language-server/src/features/diagnostics/duplicatedNode.ts b/language-server/src/features/diagnostics/duplicatedNode.ts index 4292e6e8..9c123d14 100644 --- a/language-server/src/features/diagnostics/duplicatedNode.ts +++ b/language-server/src/features/diagnostics/duplicatedNode.ts @@ -3,7 +3,7 @@ import { AsEnumerable } from 'linq-es2015' import * as tsyringe from 'tsyringe' import * as ls from 'vscode-languageserver' import winston from 'winston' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { Project } from '../../project' import jsonStr from '../../utils/json' import { RangeConverter } from '../../utils/rangeConverter' @@ -16,7 +16,7 @@ import { DiagnosticsContributor } from './contributor' @tsyringe.injectable() export class DuplicatedNode implements DiagnosticsContributor { private log = winston.createLogger({ - format: winston.format.combine(className(DuplicatedNode), logFormat), + format: winston.format.combine(withClass(DuplicatedNode)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/diagnostics/primitive.ts b/language-server/src/features/diagnostics/primitive.ts index ca4ee22e..5aba9e61 100644 --- a/language-server/src/features/diagnostics/primitive.ts +++ b/language-server/src/features/diagnostics/primitive.ts @@ -3,7 +3,7 @@ import { AsEnumerable } from 'linq-es2015' import * as tsyringe from 'tsyringe' import * as ls from 'vscode-languageserver' import winston from 'winston' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { Project } from '../../project' import { RangeConverter } from '../../utils/rangeConverter' import { getNodesBFS, isFloat, isInteger, isLeafNode } from '../utils' @@ -16,7 +16,7 @@ import { DiagnosticsContributor } from './contributor' @tsyringe.injectable() export class PrimitiveValue implements DiagnosticsContributor { private log = winston.createLogger({ - format: winston.format.combine(className(PrimitiveValue), logFormat), + format: winston.format.combine(withClass(PrimitiveValue)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/diagnostics/provider.ts b/language-server/src/features/diagnostics/provider.ts index 33e85872..712bb87a 100644 --- a/language-server/src/features/diagnostics/provider.ts +++ b/language-server/src/features/diagnostics/provider.ts @@ -4,10 +4,9 @@ import * as tsyringe from 'tsyringe' import * as ls from 'vscode-languageserver' import winston from 'winston' import { Configuration } from '../../configuration' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { Project } from '../../project' import { ProjectManager } from '../../projectManager' -import jsonStr from '../../utils/json' import { Provider } from '../provider' import { getRootElement } from '../utils' import { DiagnosticsContributor } from './contributor' @@ -21,7 +20,7 @@ export class DiagnosticsProvider implements Provider { private connection?: ls.Connection = undefined private log = winston.createLogger({ - format: winston.format.combine(className(DiagnosticsProvider), logFormat), + format: winston.format.combine(withClass(DiagnosticsProvider)), transports: [defaultLogger()], }) @@ -104,8 +103,8 @@ export class DiagnosticsProvider implements Provider { for (const dig of diagnosticsArr) { if (dig.uri === document.uri) { this.connection?.sendDiagnostics({ uri: dig.uri, diagnostics: dig.diagnostics }) - this.log.debug(`[${project.version}] send diagnostics to uri: ${dig.uri}, items: ${dig.diagnostics.length}`) - this.log.silly(`${jsonStr(dig.diagnostics)}`) + // this.log.debug(`[${project.version}] send diagnostics to uri: ${dig.uri}, items: ${dig.diagnostics.length}`) + // this.log.silly(`${jsonStr(dig.diagnostics)}`) } else { this.log.warn( `tried to send diagnostics which is not allowed in this context. target: ${dig.uri}, document: ${document.uri}` diff --git a/language-server/src/features/diagnostics/reference.ts b/language-server/src/features/diagnostics/reference.ts index f78e7cc7..6357a5b5 100644 --- a/language-server/src/features/diagnostics/reference.ts +++ b/language-server/src/features/diagnostics/reference.ts @@ -4,7 +4,7 @@ import * as tsyringe from 'tsyringe' import * as ls from 'vscode-languageserver' import * as winston from 'winston' import { DocumentWithNodeMap } from '../../documentWithNodeMap' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { Project } from '../../project' import { RangeConverter } from '../../utils/rangeConverter' import { Definition } from '../definition' @@ -15,7 +15,7 @@ import { DiagnosticsContributor } from './contributor' @tsyringe.injectable() export class Reference implements DiagnosticsContributor { private log = winston.createLogger({ - format: winston.format.combine(className(Reference), logFormat), + format: winston.format.combine(withClass(Reference)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/hover/def.ts b/language-server/src/features/hover/def.ts index 8dbabd24..ed5621e0 100644 --- a/language-server/src/features/hover/def.ts +++ b/language-server/src/features/hover/def.ts @@ -3,13 +3,13 @@ import * as tsyringe from 'tsyringe' import * as ls from 'vscode-languageserver' import { MarkupKind } from 'vscode-languageserver' import * as winston from 'winston' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { getClassNameCodeBlock } from '../utils/markdown' @tsyringe.injectable() export class DefHoverProvider { private log = winston.createLogger({ - format: winston.format.combine(className(DefHoverProvider), logFormat), + format: winston.format.combine(withClass(DefHoverProvider)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/hover/defReference.ts b/language-server/src/features/hover/defReference.ts index fa246a0a..ed8222a7 100644 --- a/language-server/src/features/hover/defReference.ts +++ b/language-server/src/features/hover/defReference.ts @@ -7,7 +7,7 @@ import { URI } from 'vscode-uri' import winston from 'winston' import { FileStore } from '../../fileStore' import { DependencyFile } from '../../fs' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { Project } from '../../project' import { RangeConverter } from '../../utils/rangeConverter' import { Definition } from '../definition' @@ -22,7 +22,7 @@ prettydiff.options.indent_char = ' ' @tsyringe.injectable() export class DefReferenceHover { private log = winston.createLogger({ - format: winston.format.combine(className(DefReferenceHover), logFormat), + format: winston.format.combine(withClass(DefReferenceHover)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/hover/hover.ts b/language-server/src/features/hover/hover.ts index 8834115b..e4956769 100644 --- a/language-server/src/features/hover/hover.ts +++ b/language-server/src/features/hover/hover.ts @@ -4,7 +4,7 @@ import * as ls from 'vscode-languageserver' import { Connection } from 'vscode-languageserver' import { URI } from 'vscode-uri' import * as winston from 'winston' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { Project } from '../../project' import { RangeConverter } from '../../utils/rangeConverter' import { Provider } from '../provider' @@ -58,7 +58,7 @@ type HoverType = 'parentNameValue' | 'defReference' | 'tag' | 'content' | 'def' @injectable() export class HoverProvider implements Provider { private log = winston.createLogger({ - format: winston.format.combine(className(HoverProvider), logFormat), + format: winston.format.combine(withClass(HoverProvider)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/hover/parentNameAttribValue.ts b/language-server/src/features/hover/parentNameAttribValue.ts index 541993ab..01cb82f2 100644 --- a/language-server/src/features/hover/parentNameAttribValue.ts +++ b/language-server/src/features/hover/parentNameAttribValue.ts @@ -7,7 +7,7 @@ import { URI } from 'vscode-uri' import winston from 'winston' import { FileStore } from '../../fileStore' import { DependencyFile } from '../../fs' -import defaultLogger, { className, logFormat } from '../../log' +import defaultLogger, { withClass } from '../../log' import { Project } from '../../project' // how to use 'prettydiff' (it is quite different to use than other standard libs) // https://github.com/prettydiff/prettydiff/issues/176 @@ -20,7 +20,7 @@ prettydiff.options.indent_char = ' ' @tsyringe.injectable() export class ParentNameAttribValueHover { private log = winston.createLogger({ - format: winston.format.combine(className(ParentNameAttribValueHover), logFormat), + format: winston.format.combine(withClass(ParentNameAttribValueHover)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/hover/tag.ts b/language-server/src/features/hover/tag.ts index 6a81e997..48dc3370 100644 --- a/language-server/src/features/hover/tag.ts +++ b/language-server/src/features/hover/tag.ts @@ -3,13 +3,13 @@ import * as tsyringe from 'tsyringe' import * as ls from 'vscode-languageserver' import { MarkupKind } from 'vscode-languageserver' import * as winston from 'winston' -import defaultLogger, { className, logFormat } from '../../log' -import { getGenericClassNameToString, getCsharpFieldCodeBlock, getClassNameCodeBlock } from '../utils/markdown' +import defaultLogger, { withClass } from '../../log' +import { getClassNameCodeBlock, getCsharpFieldCodeBlock, getGenericClassNameToString } from '../utils/markdown' @tsyringe.injectable() export class TagHoverProvider { private log = winston.createLogger({ - format: winston.format.combine(className(TagHoverProvider), logFormat), + format: winston.format.combine(withClass(TagHoverProvider)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/index.ts b/language-server/src/features/index.ts index 21da33b7..eb8a712e 100644 --- a/language-server/src/features/index.ts +++ b/language-server/src/features/index.ts @@ -5,7 +5,7 @@ import { singleton } from 'tsyringe' import * as lsp from 'vscode-languageserver' import { URI } from 'vscode-uri' import winston from 'winston' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { LoadFolder } from '../mod/loadfolders' import { ProjectManager } from '../projectManager' import { RimWorldVersionArray } from '../RimWorldVersion' @@ -22,7 +22,7 @@ import { Rename } from './rename' @singleton() export class LanguageFeature { private readonly log = winston.createLogger({ - format: winston.format.combine(className(LanguageFeature), logFormat), + format: winston.format.combine(withClass(LanguageFeature)), transports: [defaultLogger()], }) @@ -50,6 +50,10 @@ export class LanguageFeature { for (const version of versions) { const project = this.projectManager.getProject(version) + if (!project) { + continue + } + const { isIncomplete, items } = this.codeCompletion.codeCompletion(project, uri, position) result.isIncomplete ||= isIncomplete result.items.push(...items) @@ -65,6 +69,10 @@ export class LanguageFeature { for (const version of versions) { const project = this.projectManager.getProject(version) + if (!project) { + continue + } + const { definitionLinks, errors } = this.definition.onDefinition(project, uri, position) this.handleError(errors) @@ -80,6 +88,10 @@ export class LanguageFeature { for (const version of RimWorldVersionArray) { const project = this.projectManager.getProject(version) + if (!project) { + continue + } + const res = this.reference.onReference(project, uri, position) result.push(...res) @@ -97,6 +109,10 @@ export class LanguageFeature { for (const version of RimWorldVersionArray) { const project = this.projectManager.getProject(version) + if (!project) { + continue + } + const res = this.rename.rename(project, uri, newName, position) edit.changes = _.merge(edit.changes, res) } diff --git a/language-server/src/features/reference.ts b/language-server/src/features/reference.ts index 22c3b929..0f21e755 100644 --- a/language-server/src/features/reference.ts +++ b/language-server/src/features/reference.ts @@ -9,7 +9,7 @@ import * as lsp from 'vscode-languageserver' import { URI } from 'vscode-uri' import * as winston from 'winston' import { DefManager } from '../defManager' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { Project } from '../project' import { RangeConverter } from '../utils/rangeConverter' import { getRootInProject } from './utils' @@ -19,7 +19,7 @@ import { toAttribValueRange, toRange } from './utils/range' @injectable() export class Reference { private log = winston.createLogger({ - format: winston.format.combine(className(Reference), logFormat), + format: winston.format.combine(withClass(Reference)), transports: [defaultLogger()], }) diff --git a/language-server/src/features/utils/project.ts b/language-server/src/features/utils/project.ts index 0fb85683..e0c4ce9d 100644 --- a/language-server/src/features/utils/project.ts +++ b/language-server/src/features/utils/project.ts @@ -20,13 +20,21 @@ import { getDefs, getDefsNode } from './node' export class ProjectHelper { constructor(protected readonly loadFolder: LoadFolder, protected readonly projectManager: ProjectManager) {} - getProjects(uri: string | URI) { + getProjects(uri: string | URI): Project[] { + const projects: Project[] = [] + const versions = this.getVersions(uri) + for (const version of versions) { + const project = this.projectManager.getProject(version) + if (project) { + projects.push(project) + } + } - return versions.map((ver) => this.projectManager.getProject(ver)) + return projects } - getVersions(uri: string | URI) { + getVersions(uri: string | URI): string[] { if (typeof uri === 'string') { uri = URI.parse(uri) } diff --git a/language-server/src/fileStore.ts b/language-server/src/fileStore.ts index a894fa21..a19a4502 100644 --- a/language-server/src/fileStore.ts +++ b/language-server/src/fileStore.ts @@ -6,7 +6,7 @@ import TypedEventEmitter from 'typed-emitter' import { DefaultDictionary } from 'typescript-collections' import * as winston from 'winston' import { File, FileCreateParameters } from './fs' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass } from './log' import { NotificationEvents } from './notificationEventManager' import { Result } from './utils/functional/result' @@ -15,7 +15,7 @@ type Events = NotificationEvents @singleton() export class FileStore { private log = winston.createLogger({ - format: winston.format.combine(className(FileStore), logFormat), + format: winston.format.combine(withClass(FileStore)), transports: [defaultLogger()], }) diff --git a/language-server/src/fs/reader.ts b/language-server/src/fs/reader.ts index f286068d..023aa937 100644 --- a/language-server/src/fs/reader.ts +++ b/language-server/src/fs/reader.ts @@ -4,13 +4,13 @@ import { Connection } from 'vscode-languageserver' import * as winston from 'winston' import { ConnectionToken } from '../connection' import { TextRequest } from '../events' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { File } from './file' @injectable() export class TextReader { private log = winston.createLogger({ - format: winston.format.combine(className(TextReader), logFormat), + format: winston.format.combine(withClass(TextReader)), transports: [defaultLogger()], }) diff --git a/language-server/src/log.ts b/language-server/src/log.ts index 884ddff0..0bc22adf 100644 --- a/language-server/src/log.ts +++ b/language-server/src/log.ts @@ -7,8 +7,10 @@ const DEFAULT_LOG_LEVEL = 'info' @tsyringe.singleton() export class LogManager { + readonly transport = new winston.transports.Console() + readonly defaultLogger = winston.createLogger({ - format: format.combine(logFormat), + format: logFormat, level: DEFAULT_LOG_LEVEL, transports: [new winston.transports.Console()], }) @@ -44,15 +46,33 @@ export class LogManager { } } -export const className = format((info, classType?: new (...p: any[]) => any) => { - info.className = classType?.name ?? 'NONTYPE' +export const withClass = format((data, classType?: new (...p: any[]) => any) => { + data.className = classType?.name ?? 'NONTYPE' - return info + return data }) -export const logFormat = format.printf(({ level, className, id, message }) => - id ? `[${level}]\t[${className}]\t(${id}):\t${message}` : `[${level}]\t[${className}]:\t${message}` -) +export const withVersion = format((data, version) => { + data.version = version + + return data +}) + +const logFormat = format.printf(({ level, className, version, message }) => { + const tags: string[] = [] + + tags.push(`[${level}]`) + if (className) { + tags.push(`[${className}]`) + } + if (version) { + tags.push(`[${version}]`) + } + + const tag = tags.join('\t') + + return `${tag}:\t${message}` +}) export default function defaultLogger(): winston.Logger { return tsyringe.container.resolve(LogManager).defaultLogger diff --git a/language-server/src/mod/about.ts b/language-server/src/mod/about.ts index 17d51512..bb12769c 100644 --- a/language-server/src/mod/about.ts +++ b/language-server/src/mod/about.ts @@ -6,7 +6,7 @@ import TypedEventEmitter from 'typed-emitter' import { URI } from 'vscode-uri' import * as winston from 'winston' import { File, XMLFile } from '../fs' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { NotificationEvents } from '../notificationEventManager' import { RimWorldVersionArray } from '../RimWorldVersion' import { xml } from '../utils' @@ -19,7 +19,7 @@ export type AboutEvents = { @singleton() export class About { private log = winston.createLogger({ - format: winston.format.combine(className(About), logFormat), + format: winston.format.combine(withClass(About)), transports: [defaultLogger()], }) diff --git a/language-server/src/mod/aboutMetadata.ts b/language-server/src/mod/aboutMetadata.ts index 494e89ad..24448b6f 100644 --- a/language-server/src/mod/aboutMetadata.ts +++ b/language-server/src/mod/aboutMetadata.ts @@ -8,7 +8,7 @@ import { URI } from 'vscode-uri' import winston from 'winston' import { FileStore } from '../fileStore' import { File, XMLFile } from '../fs' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { NotificationEventManager } from '../notificationEventManager' import jsonStr from '../utils/json' import * as xml from '../utils/xml' @@ -66,7 +66,7 @@ export class AboutMetadata { static readonly fileName = 'metadata_rwxml.xml' private log = winston.createLogger({ - format: winston.format.combine(className(AboutMetadata), logFormat), + format: winston.format.combine(withClass(AboutMetadata)), transports: [defaultLogger()], }) diff --git a/language-server/src/mod/loadfolders.ts b/language-server/src/mod/loadfolders.ts index 89b8a689..47110555 100644 --- a/language-server/src/mod/loadfolders.ts +++ b/language-server/src/mod/loadfolders.ts @@ -8,7 +8,7 @@ import { URI } from 'vscode-uri' import * as winston from 'winston' import { FileStore } from '../fileStore' import { File, XMLFile } from '../fs' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { NotificationEventManager, NotificationEvents } from '../notificationEventManager' import { RimWorldVersion, RimWorldVersionArray } from '../RimWorldVersion' import { xml } from '../utils' @@ -26,7 +26,7 @@ type Events = { @tsyringe.singleton() export class LoadFolder { private log = winston.createLogger({ - format: winston.format.combine(className(LoadFolder), logFormat), + format: winston.format.combine(withClass(LoadFolder)), transports: [defaultLogger()], }) diff --git a/language-server/src/mod/modDependencyBags.ts b/language-server/src/mod/modDependencyBags.ts index 56e98977..49f15e91 100644 --- a/language-server/src/mod/modDependencyBags.ts +++ b/language-server/src/mod/modDependencyBags.ts @@ -12,7 +12,7 @@ import winston from 'winston' import { ConnectionToken } from '../connection' import { DependencyRequest, DependencyRequestResponse } from '../events' import { FileStore } from '../fileStore' -import defaultLogger, { className, logFormat } from '../log' +import defaultLogger, { withClass } from '../log' import { Result } from '../utils/functional/result' import { About } from './about' import { AboutMetadata } from './aboutMetadata' @@ -170,7 +170,7 @@ type Events = { @tsyringe.singleton() export class ModDependencyBags { private log = winston.createLogger({ - format: winston.format.combine(className(ModDependencyBags), logFormat), + format: winston.format.combine(withClass(ModDependencyBags)), transports: [defaultLogger()], }) diff --git a/language-server/src/notificationEventManager.ts b/language-server/src/notificationEventManager.ts index 76ef15fa..05cbd777 100644 --- a/language-server/src/notificationEventManager.ts +++ b/language-server/src/notificationEventManager.ts @@ -1,10 +1,10 @@ import EventEmitter from 'events' import * as tsyringe from 'tsyringe' +import TypedEventEmitter from 'typed-emitter' import { URI } from 'vscode-uri' -import { File } from './fs' import * as winston from 'winston' -import TypedEventEmitter from 'typed-emitter' -import defaultLogger, { className, logFormat } from './log' +import { File } from './fs' +import defaultLogger, { withClass } from './log' // events that this manager will emit export type NotificationEvents = { @@ -19,7 +19,7 @@ export type NotificationEvents = { @tsyringe.singleton() export class NotificationEventManager { private log = winston.createLogger({ - format: winston.format.combine(className(NotificationEventManager), logFormat), + format: winston.format.combine(withClass(NotificationEventManager)), transports: [defaultLogger()], }) diff --git a/language-server/src/project.ts b/language-server/src/project.ts index 9e3e5fab..82da689e 100644 --- a/language-server/src/project.ts +++ b/language-server/src/project.ts @@ -5,14 +5,13 @@ import _ from 'lodash' import * as ono from 'ono' import { inject, Lifecycle, scoped } from 'tsyringe' import TypedEventEmitter from 'typed-emitter' -import { v4 as uuid } from 'uuid' import { CancellationToken, CancellationTokenSource } from 'vscode-languageserver' import { TextDocument } from 'vscode-languageserver-textdocument' import { URI } from 'vscode-uri' import * as winston from 'winston' import { DefManager } from './defManager' import * as documentWithNodeMap from './documentWithNodeMap' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass, withVersion } from './log' import { About } from './mod' import { ResourceStore } from './resourceStore' import { RimWorldVersion, RimWorldVersionToken } from './RimWorldVersion' @@ -40,7 +39,7 @@ type Events = { @scoped(Lifecycle.ContainerScoped) export class Project { private log = winston.createLogger({ - format: winston.format.combine(className(Project), logFormat), + format: winston.format.combine(withClass(Project), withVersion(this.version)), transports: [defaultLogger()], }) @@ -109,13 +108,12 @@ export class Project { * uses debounce to limit reloading too often */ private reloadProject = _.debounce(async (reason = '') => { - const requestId = uuid() if (this.state === 'reloading') { this.reloadProject(reason) return } - this.log.info(`reloading project... reason: ${reason}`, { id: requestId }) + this.log.info(`reloading project... reason: ${reason}`) this._state = 'reloading' this.cancelTokenSource.cancel() @@ -123,26 +121,27 @@ export class Project { this.cancelTokenSource = cancelTokenSource const cancelToken = this.cancelTokenSource.token - this.log.info(`loading project resources...`, { id: requestId }) + this.log.info(`loading project resources...`) this.resourceStore.reload('project reload') - this.log.info(`clear project...`, { id: requestId }) - const err = await this.reset(requestId, cancelToken) + this.log.info(`clear project...`) + const err = await this.reset(cancelToken) if (cancelToken.isCancellationRequested) { - this.log.info(`project evluation canceled.`, { id: requestId }) + this.log.info(`project evluation canceled.`) cancelTokenSource.dispose() return } if (err) { this.log.error(`failed reset project. err: ${err}`) + this._state = 'invalid' } else { - this.log.info(`project cleared.`, { id: requestId }) + this.log.info(`project cleared.`) this.evaluteProject() this._state = 'ready' this.event.emit('projectReloaded') - this.log.info(`project evaluated.`, { id: requestId }) + this.log.info(`project evaluated.`) } cancelTokenSource.dispose() @@ -155,23 +154,19 @@ export class Project { /** * reset project to initial state */ - private async reset(requestId: string = uuid(), cancelToken?: CancellationToken): Promise { - this.log.debug( - // TODO: put uuid as log format - `current project file dll count: ${this.resourceStore.dllFiles.size}`, - { id: requestId } - ) + private async reset(cancelToken?: CancellationToken): Promise { + this.log.debug(`current project file dll count: ${this.resourceStore.dllFiles.size}`) this.log.silly( `dll files: ${jsonStr([...this.resourceStore.dllFiles].map((uri) => decodeURIComponent(uri.toString())))}` ) - const [typeInfoMap, err0] = await this.getTypeInfo(requestId) + const [typeInfoMap, err0] = await this.getTypeInfo() if (cancelToken?.isCancellationRequested) { - return ono.ono(`[${requestId}] request canceled.`) + return ono.ono(`[${this.version}] request canceled.`) } if (err0) { - return ono.ono(`[${requestId}] failed fetching typeInfoMap. error: ${jsonStr(err0)}`) + return ono.ono(`[${this.version}] failed fetching typeInfoMap. error: ${jsonStr(err0)}`) } this.xmls = new Map() @@ -180,8 +175,8 @@ export class Project { return null } - async getTypeInfo(requestId: string = uuid()): Promise<[TypeInfoMap, Error | null]> { - return this.typeInfoMapProvider.get(requestId) + async getTypeInfo(): Promise<[TypeInfoMap, Error | null]> { + return this.typeInfoMapProvider.get() } /** diff --git a/language-server/src/projectManager.ts b/language-server/src/projectManager.ts index c6e18bfb..1339e972 100644 --- a/language-server/src/projectManager.ts +++ b/language-server/src/projectManager.ts @@ -4,7 +4,7 @@ import * as tsyringe from 'tsyringe' import TypedEventEmitter from 'typed-emitter' import * as winston from 'winston' import { File } from './fs' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass } from './log' import { About } from './mod' import { NotificationEvents } from './notificationEventManager' import { Project } from './project' @@ -22,7 +22,7 @@ type Events = { @tsyringe.singleton() export class ProjectManager { private log = winston.createLogger({ - format: winston.format.combine(className(ProjectManager), logFormat), + format: winston.format.combine(withClass(ProjectManager)), transports: [defaultLogger()], }) @@ -35,7 +35,7 @@ export class ProjectManager { public readonly events = new EventEmitter() as TypedEventEmitter - constructor(about: About) { + constructor(private readonly about: About) { about.event.on('aboutChanged', this.onAboutChanged.bind(this)) } @@ -65,7 +65,11 @@ export class ProjectManager { this.log.info(`supportedVersions deleted: ${jsonStr(deleted)}`) } - getProject(version: string): Project { + getProject(version: string): Project | null { + if (!this.about.supportedVersions.includes(version)) { + return null + } + const c = this.getOrCreateContainer(version) return c.resolve(Project) } diff --git a/language-server/src/resourceStore.ts b/language-server/src/resourceStore.ts index 01c29c4e..51f29a69 100644 --- a/language-server/src/resourceStore.ts +++ b/language-server/src/resourceStore.ts @@ -18,7 +18,7 @@ import { TextureFile, XMLFile, } from './fs' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass, withVersion } from './log' import { LoadFolder } from './mod/loadfolders' import { ModDependencyBags } from './mod/modDependencyBags' import { ProjectWorkspace } from './mod/projectWorkspace' @@ -41,7 +41,7 @@ type Events = { @scoped(Lifecycle.ContainerScoped) export class ResourceStore { private log = winston.createLogger({ - format: winston.format.combine(className(ResourceStore), logFormat), + format: winston.format.combine(withClass(ResourceStore), withVersion(this.version)), transports: [defaultLogger()], }) diff --git a/language-server/src/test/1.4/defRef.test.ts b/language-server/src/test/1.4/defRef.test.ts index 2c4a41b0..0bef0465 100644 --- a/language-server/src/test/1.4/defRef.test.ts +++ b/language-server/src/test/1.4/defRef.test.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { DefDatabase, NameDatabase, parse, TypeInfoLoader, TypeInfoMap } from '@rwxml/analyzer' +import { Def, DefDatabase, Injectable, NameDatabase, parse, Text, TypeInfoLoader, TypeInfoMap } from '@rwxml/analyzer' import 'reflect-metadata' import { container } from 'tsyringe' import { DefManager } from '../../defManager' @@ -8,11 +8,7 @@ import { Definition } from '../../features/definition' import typeInfo from './typeInfo.json' describe('def reference test', () => { - let typeMap: TypeInfoMap - - beforeAll(() => { - typeMap = TypeInfoLoader.load((typeInfo as any).rawData) - }) + const typeMap: TypeInfoMap = TypeInfoLoader.load((typeInfo as any).rawData) beforeEach(() => { container.clearInstances() @@ -65,4 +61,104 @@ describe('def reference test', () => { expect(location).not.toBeNull() expect(location).toHaveLength(1) }) + + describe('it should be able to resolve ref field added in 1.4', () => { + const ref = ` + + + + AT_Wall + Heavy + +
  • Metallic
  • + +
  • Minimal
  • +
    + +
  • + CompShipHeat +
  • + + +
  • + Thump + 0.5 +
  • +
    +
    +
    +` + + const src = ` + + + + Heavy + + + + Metallic + + + + MeditationFocusStrength + + + + Minimal + + + + Thump + + +` + + const defManager = new DefManager(new DefDatabase(), new NameDatabase(), typeMap, '1.4') + const refXML = documentWithNodeMap.create(parse(ref, 'target.xml')) + defManager.update(refXML) + + const srcXML = documentWithNodeMap.create(parse(src, 'src.xml')) + defManager.update(srcXML) + + // HACK + const definition = new Definition(null as any) + + it('injector should inject StuffCategoryDef', () => { + const stuffCategoryDefNode = srcXML.findNodeAt(149)! + expect(stuffCategoryDefNode).toBeDefined() + expect(stuffCategoryDefNode).toBeInstanceOf(Def) + }) + + it('test search of "TerrainAffordanceDef"', () => { + const metallicNode = refXML.findNodeAt(241)! + expect(metallicNode).toBeDefined() + expect(metallicNode).toBeInstanceOf(Text) + expect(metallicNode?.parent).toBeInstanceOf(Injectable) + + const defs = definition.findDefinitions(defManager, refXML, 241) + expect(defs).toHaveLength(1) + }) + + it('test search of "StuffCategoryDef"', () => { + // TODO + }) + + it('test search of "StatDef"', () => { + // TODO + }) + + it('test search of "StyleItemCategoryDef"', () => { + // TODO + }) + + it('test search of "DamageDef"', () => { + // TODO + }) + }) }) diff --git a/language-server/src/textDocumentManager.ts b/language-server/src/textDocumentManager.ts index 6d6c647a..96157341 100644 --- a/language-server/src/textDocumentManager.ts +++ b/language-server/src/textDocumentManager.ts @@ -8,7 +8,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument' import * as winston from 'winston' import { FileStore } from './fileStore' import { File, TextFile } from './fs' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass } from './log' import { NotificationEvents } from './notificationEventManager' import { TextDocumentsAdapter } from './textDocumentsAdapter' import { Result } from './utils/functional/result' @@ -26,7 +26,7 @@ type Events = { @tsyringe.singleton() export class TextDocumentManager { private log = winston.createLogger({ - format: winston.format.combine(className(TextDocumentManager), logFormat), + format: winston.format.combine(withClass(TextDocumentManager)), transports: [defaultLogger()], }) diff --git a/language-server/src/textDocumentsAdapter.ts b/language-server/src/textDocumentsAdapter.ts index be84936b..0e042695 100644 --- a/language-server/src/textDocumentsAdapter.ts +++ b/language-server/src/textDocumentsAdapter.ts @@ -8,7 +8,7 @@ import { URI } from 'vscode-uri' import * as winston from 'winston' import { ConnectionToken } from './connection' import { FileStore } from './fileStore' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass } from './log' type Events = { textDocumentChanged(document: TextDocument): void @@ -23,7 +23,7 @@ type Events = { @singleton() export class TextDocumentsAdapter { private log = winston.createLogger({ - format: winston.format.combine(className(TextDocumentsAdapter), logFormat), + format: winston.format.combine(withClass(TextDocumentsAdapter)), transports: [defaultLogger()], }) diff --git a/language-server/src/typeInfoMapProvider.ts b/language-server/src/typeInfoMapProvider.ts index 6e45b1c4..814c6b39 100644 --- a/language-server/src/typeInfoMapProvider.ts +++ b/language-server/src/typeInfoMapProvider.ts @@ -2,12 +2,11 @@ import { TypeInfo, TypeInfoLoader, TypeInfoMap } from '@rwxml/analyzer' import ono from 'ono' import { delay, inject, Lifecycle, scoped } from 'tsyringe' -import { v4 as uuid } from 'uuid' import { Connection } from 'vscode-languageserver' import * as winston from 'winston' import { ConnectionToken } from './connection' import { TypeInfoRequest } from './events' -import defaultLogger, { className, logFormat } from './log' +import defaultLogger, { withClass } from './log' import { Project } from './project' import { RimWorldVersion, RimWorldVersionToken } from './RimWorldVersion' import jsonStr from './utils/json' @@ -15,7 +14,7 @@ import jsonStr from './utils/json' @scoped(Lifecycle.ContainerScoped) export class TypeInfoMapProvider { private log = winston.createLogger({ - format: winston.format.combine(className(TypeInfoMapProvider), logFormat), + format: winston.format.combine(withClass(TypeInfoMapProvider)), transports: [defaultLogger()], }) @@ -25,17 +24,17 @@ export class TypeInfoMapProvider { @inject(delay(() => Project)) private readonly project: Project ) {} - async get(requestId: string = uuid()): Promise<[TypeInfoMap, Error | null]> { + async get(): Promise<[TypeInfoMap, Error | null]> { const dllUris = this.getTargetDLLUris() - this.log.debug(`requesting typeInfo. count: ${dllUris.length}`, { id: requestId }) + this.log.debug(`requesting typeInfo. count: ${dllUris.length}`, { id: this.version }) this.log.silly(`uris: ${jsonStr(dllUris)}`) const typeInfos = await this.requestTypeInfos(dllUris) if (typeInfos instanceof Error) { return [new TypeInfoMap(), typeInfos] } - this.log.debug(`received typeInfo from client, length: ${typeInfos.length}`, { id: requestId }) + this.log.debug(`received typeInfo from client, length: ${typeInfos.length}`, { id: this.version }) const typeInfoMap = TypeInfoLoader.load(typeInfos) @@ -48,7 +47,8 @@ export class TypeInfoMapProvider { private async requestTypeInfos(uris: string[]): Promise[] | Error> { try { - return (await this.connection.sendRequest(TypeInfoRequest, { uris })).data as Partial[] + return (await this.connection.sendRequest(TypeInfoRequest, { uris, version: this.version })) + .data as Partial[] } catch (err) { // TODO: error handling return ono(err as any, 'failed to request typeInfo') diff --git a/vsc-extension/package.json b/vsc-extension/package.json index 15f5e7ef..ac29c41d 100644 --- a/vsc-extension/package.json +++ b/vsc-extension/package.json @@ -162,6 +162,7 @@ "double-ended-queue": "^2.1.0-0", "fast-glob": "^3.2.7", "fast-safe-stringify": "^2.1.1", + "get-file-properties": "^1.0.1", "htmlparser2": "^7.0.0", "js-yaml": "^4.1.0", "linq-es2015": "^2.5.1", diff --git a/vsc-extension/pnpm-lock.yaml b/vsc-extension/pnpm-lock.yaml index bb580df7..dc414ab2 100644 --- a/vsc-extension/pnpm-lock.yaml +++ b/vsc-extension/pnpm-lock.yaml @@ -23,6 +23,7 @@ specifiers: eslint-config-typescript: ^3.0.0 fast-glob: ^3.2.7 fast-safe-stringify: ^2.1.1 + get-file-properties: ^1.0.1 htmlparser2: ^7.0.0 jest: ^26.3.0 jest-raw-loader: ^1.0.1 @@ -61,6 +62,7 @@ dependencies: double-ended-queue: 2.1.0-0 fast-glob: 3.2.7 fast-safe-stringify: 2.1.1 + get-file-properties: 1.0.1 htmlparser2: 7.1.2 js-yaml: 4.1.0 linq-es2015: 2.5.1 @@ -2507,6 +2509,10 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: true + /get-file-properties/1.0.1: + resolution: {integrity: sha512-46xG/mXgpE1T0sCh/dg10t1DKecxyLyUUyy0UwYsFIoEX5yKFSIdnyY5FGC9uyF3pT7BgJ4hyurbmdxyXXd5cw==} + dev: false + /get-package-type/0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} diff --git a/vsc-extension/src/events/events.ts b/vsc-extension/src/events/events.ts index 6bdfcf19..4d73f6dc 100644 --- a/vsc-extension/src/events/events.ts +++ b/vsc-extension/src/events/events.ts @@ -31,6 +31,7 @@ export interface TextRequestResponse { } export interface TypeInfoRequest { + version: string uris: string[] } diff --git a/vsc-extension/src/mod/pathStore.ts b/vsc-extension/src/mod/pathStore.ts index a13fe91c..ce73167d 100644 --- a/vsc-extension/src/mod/pathStore.ts +++ b/vsc-extension/src/mod/pathStore.ts @@ -84,6 +84,10 @@ export abstract class PathStore { ) } + get RimWorldCoreDLLPath(): string { + return path.join(this.RimWorldManagedDirectory, 'Assembly-CSharp.dll') + } + get WorkshopModDirectory(): string { return this.getOrDefault( vscode.workspace.getConfiguration('rwxml.paths').get('workshopMods'), diff --git a/vsc-extension/src/mod/version.ts b/vsc-extension/src/mod/version.ts index 081625da..9a95fe66 100644 --- a/vsc-extension/src/mod/version.ts +++ b/vsc-extension/src/mod/version.ts @@ -1,4 +1,7 @@ -export const RimWorldVersions = ['default', '1.0', '1.1', '1.2', '1.3'] as const +// TODO: fix this +const RimWorldVersionArray = ['1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', 'default'] as const + +export const RimWorldVersions = RimWorldVersionArray export type RimWorldVersion = typeof RimWorldVersions[number] diff --git a/vsc-extension/src/resources/cachedTypeInfoProvider.ts b/vsc-extension/src/resources/cachedTypeInfoProvider.ts index 4c64b5f8..73bf9e87 100644 --- a/vsc-extension/src/resources/cachedTypeInfoProvider.ts +++ b/vsc-extension/src/resources/cachedTypeInfoProvider.ts @@ -8,7 +8,6 @@ import * as os from 'os' import * as path from 'path' import * as semver from 'semver' import { inject, injectable } from 'tsyringe' -import { v4 as uuid } from 'uuid' import * as vscode from 'vscode' import { LanguageClient } from 'vscode-languageclient' import winston from 'winston' @@ -86,8 +85,8 @@ export class CachedTypeInfoProvider implements Provider { /** * @todo check cache confliction on write */ - private async onTypeInfoRequest({ uris }: TypeInfoRequest): Promise { - const requestId = uuid() + private async onTypeInfoRequest({ uris, version }: TypeInfoRequest): Promise { + this.log.debug(`received type info request. version: ${version}`) uris.sort() @@ -126,16 +125,16 @@ export class CachedTypeInfoProvider implements Provider { const cacheData = await checkCacheValid() let data = cacheData.data if (!cacheData.valid) { - this.log.debug('checksum invalid, updating cache.', { id: requestId }) + this.log.debug('checksum invalid, updating cache.') - const res = await this.typeInfoProvider.onTypeInfoRequest({ uris }) + const res = await this.typeInfoProvider.onTypeInfoRequest({ uris, version }) if (res instanceof Error) { throw res } data = res.data - await this.updateCache(cachePath, uris, data, requestId) + await this.updateCache(cachePath, uris, data) } else { this.log.silly(`cache hit! uris: ${jsonStr(uris)}`) } @@ -177,7 +176,7 @@ export class CachedTypeInfoProvider implements Provider { return [...files.map((uri) => vscode.Uri.parse(uri).fsPath).map(md5sum)] } - private async updateCache(cachePath: string, files: string[], data: any, requestId: string): Promise { + private async updateCache(cachePath: string, files: string[], data: any): Promise { try { const checksums = await this.getChecksums(files) @@ -193,9 +192,9 @@ export class CachedTypeInfoProvider implements Provider { const raw = jsonStr(cache) await fs.writeFile(cachePath, raw, { encoding: 'utf-8', flag: 'w+', mode: '644' }) - this.log.debug(`write cache to file: ${jsonStr(cachePath)}`, { id: requestId }) + this.log.debug(`write cache to file: ${jsonStr(cachePath)}`) } catch (err) { - this.log.error(`failed to write cache to file: ${cachePath}, err: ${jsonStr(err)}`, { id: requestId }) + this.log.error(`failed to write cache to file: ${cachePath}, err: ${jsonStr(err)}`) } } } diff --git a/vsc-extension/src/resources/typeInfoProvider.ts b/vsc-extension/src/resources/typeInfoProvider.ts index 180dd79d..75dadc3b 100644 --- a/vsc-extension/src/resources/typeInfoProvider.ts +++ b/vsc-extension/src/resources/typeInfoProvider.ts @@ -1,6 +1,10 @@ +import { spawnSync } from 'child_process' +import { getFileProperties } from 'get-file-properties' +import ono from 'ono' +import * as semver from 'semver' import * as tsyringe from 'tsyringe' import * as vscode from 'vscode' -import { LanguageClient } from 'vscode-languageclient' +import { LanguageClient, ResponseError } from 'vscode-languageclient' import winston from 'winston' import { TypeInfoRequest, TypeInfoRequestResponse } from '../events' import { className, log, logFormat } from '../log' @@ -17,6 +21,8 @@ export class TypeInfoProvider implements Provider { transports: [log], }) + private static readonly defaultVersion = new semver.SemVer('0.0.0') + constructor(@tsyringe.inject(mod.PathStore.token) private readonly pathStore: mod.PathStore) {} async listen(client: LanguageClient): Promise { @@ -27,7 +33,26 @@ export class TypeInfoProvider implements Provider { private requestCounter = 0 private clearProgress: (() => void) | null = null - async onTypeInfoRequest({ uris }: TypeInfoRequest): Promise { + async onTypeInfoRequest({ uris, version }: TypeInfoRequest): Promise> { + this.log.info('received TypeInfo request.') + + const CoreDLLPath = this.pathStore.RimWorldCoreDLLPath + const semverVersion = this.parseVersion(version) + if (!semverVersion) { + const err = ono(`cannot parse version: ${version}`) + this.log.error(err) + return new ResponseError(1, err.message, err) + } + + const isCoreDLLVersionCorrect = await this.checkCoreDLLVersion(CoreDLLPath, semverVersion) + if (!isCoreDLLVersionCorrect) { + const err = ono( + `Core DLL version mismatch. expected: ${semverVersion}, actual: ${await this.getCoreDLLVersion(CoreDLLPath)}` + ) + this.log.error(err) + return new ResponseError(2, err.message, err) + } + if (!this.clearProgress) { const { resolve } = await createProgress({ location: vscode.ProgressLocation.Notification, @@ -41,7 +66,8 @@ export class TypeInfoProvider implements Provider { this.requestCounter -= 1 if (this.requestCounter < 0) { - throw Error() + this.requestCounter = 0 + this.log.error('request counter is negative.') } if (this.requestCounter === 0) { @@ -52,7 +78,7 @@ export class TypeInfoProvider implements Provider { if (typeInfo instanceof Error) { log.error(typeInfo) - return typeInfo + return new ResponseError(3, typeInfo.message, typeInfo) } else { return { data: typeInfo } } @@ -62,10 +88,68 @@ export class TypeInfoProvider implements Provider { const dllPaths = uris.map((uri) => vscode.Uri.parse(uri).fsPath) // single .dll file or directory const managedDirectory = this.pathStore.RimWorldManagedDirectory + this.log.debug('managed directory: ', managedDirectory) dllPaths.push(managedDirectory) this.log.debug(`extracting typeinfos from: ${jsonStr(dllPaths)}`) return await extractTypeInfos(...dllPaths) } + + async checkCoreDLLVersion(DLLPath: string, version: semver.SemVer): Promise { + this.log.debug('checking Core DLL version...') + + const fileVersionOrErr = await this.getCoreDLLVersion(DLLPath) + if (fileVersionOrErr instanceof Error) { + this.log.error(`failed to get Core DLL version. err: ${fileVersionOrErr}`) + return false + } + + const fileVersion = semver.coerce(fileVersionOrErr) + if (!fileVersion) { + this.log.error(`failed to parse Core DLL version. version: ${fileVersionOrErr}`) + return false + } + + return fileVersion.major === version.major && fileVersion.minor === version.minor + } + + async getCoreDLLVersion(DLLPath: string): Promise { + switch (process.platform) { + case 'win32': + return (await this.getFileVersionWindows(DLLPath)) ?? Error('RWXML: failed to get Core DLL version') + + case 'linux': + return await this.getFileVersionLinux(DLLPath) + + default: + return Error(`Unsupported platform: ${process.platform}`) + } + } + + private parseVersion(version: string): semver.SemVer | null { + if (version === 'default') { + return TypeInfoProvider.defaultVersion + } else { + return semver.coerce(version) + } + } + + private async getFileVersionWindows(path: string): Promise { + const properties = await getFileProperties(path) + return properties.Version ?? null + } + + private async getFileVersionLinux(path: string): Promise { + try { + const process = spawnSync('strings', [path, '-el'], { stdio: 'pipe' }) + const stdout = process.stdout.toString('utf-8') + const lines = stdout.split('\n') + + const index = lines.findIndex((value) => value === 'Assembly Version') + return lines[index + 1] + } catch (err: unknown) { + return ono(err as any) + } + } } diff --git a/vsc-extension/src/typeInfo/index.ts b/vsc-extension/src/typeInfo/index.ts index 55b2eb0c..9c08301b 100644 --- a/vsc-extension/src/typeInfo/index.ts +++ b/vsc-extension/src/typeInfo/index.ts @@ -6,7 +6,7 @@ export * from './extract' const dotnetName = getDotnetName() -function getDotnetName() { +function getDotnetName(): string | null { switch (process.platform) { case 'win32': return '.NET Framework' @@ -14,9 +14,10 @@ function getDotnetName() { case 'darwin': case 'linux': return 'Mono (.NET Framework for Linux/OS X)' - } - return + default: + return null + } } export function checkTypeInfoAnalyzeAvailable(): boolean { @@ -30,7 +31,7 @@ export function checkTypeInfoAnalyzeAvailable(): boolean { return available } -async function promptDotnetInstall() { +async function promptDotnetInstall(): Promise { if (dotnetName) { const message = `\ ${dotnetName} is not installed. to enable Runtime TypeInfo Extraction, you must have ${dotnetName} with version higher than 4.0.\ @@ -60,18 +61,18 @@ function dotnetAvailable(): boolean { } } -function getDotnetVersionWindows() { +function getDotnetVersionWindows(): semver.SemVer | undefined { // windows has \r\n, need .trim() const stdout = execSync('dotnet --version', { encoding: 'utf-8' }).trim() return semver.parse(stdout) ?? undefined } -function getDotnetVersionLinux() { +function getDotnetVersionLinux(): string { const stdout = execSync('mono --version', { encoding: 'utf-8' }).trim() return stdout ?? undefined } -function getDotnetVersionOSX() { +function getDotnetVersionOSX(): string { const stdout = execSync('mono --version', { encoding: 'utf-8' }).trim() return stdout ?? undefined }