From ca643b697096c2a5aec1252a518da3747328efe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 30 Apr 2024 14:15:12 +0200 Subject: [PATCH 1/8] refactor: split VSCode to be able to use only some parts It allows to not have to call 'localize' ourselves --- src/extensions.ts | 12 +- src/service-override/files.ts | 54 +- src/service-override/localization.ts | 76 +-- src/tools/l10n.ts | 185 ------ ...ode-to-be-able-to-import-only-requir.patch | 546 ++++++++++++++++++ 5 files changed, 575 insertions(+), 298 deletions(-) delete mode 100644 src/tools/l10n.ts create mode 100644 vscode-paches/0057-refactor-split-code-to-be-able-to-import-only-requir.patch diff --git a/src/extensions.ts b/src/extensions.ts index 61290d51..1c29c7a9 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -14,12 +14,14 @@ import { IFileService } from 'vs/platform/files/common/files.service' import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement.service' import { StandaloneServices } from 'vs/editor/standalone/browser/standaloneServices' +import { ExtensionManifestTranslator, NlsConfiguration } from 'vs/platform/extensionManagement/common/extensionsScannerService' +import * as platform from 'vs/base/common/platform' import { IExtensionWithExtHostKind, ExtensionServiceOverride } from './service-override/extensions' import { CustomSchemas, registerExtensionFile } from './service-override/files' import { waitServicesReady } from './lifecycle' -import { ExtensionManifestTranslator } from './tools/l10n' import { throttle, memoized } from './tools' import { setDefaultApi } from './extension.api' +import { getBuiltInExtensionTranslationsUris } from './l10n' export type ApiFactory = (extensionId?: string) => Promise @@ -126,7 +128,13 @@ export function registerExtension (manifest: IExtensionManifest, extHostKind?: E const instantiationService = StandaloneServices.get(IInstantiationService) const translator = instantiationService.createInstance(ExtensionManifestTranslator) - const localizedManifest = await translator.translateManifest(realLocation, manifest) + const nlsConfiguration: NlsConfiguration = { + devMode: false, + language: platform.language, + pseudo: platform.language === 'pseudo', + translations: getBuiltInExtensionTranslationsUris(platform.language) ?? {} + } + const localizedManifest = await translator.translateManifest(realLocation, manifest, nlsConfiguration) const extension: IExtensionWithExtHostKind = { manifest: localizedManifest, diff --git a/src/service-override/files.ts b/src/service-override/files.ts index c66682f4..8fa85b26 100644 --- a/src/service-override/files.ts +++ b/src/service-override/files.ts @@ -1,12 +1,12 @@ import { IEditorOverrideServices, StandaloneServices } from 'vs/editor/standalone/browser/standaloneServices' import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' -import { FileService } from 'vs/platform/files/common/fileService' +import { FileService, mkdirp } from 'vs/platform/files/common/fileService' import { LogLevel } from 'vs/platform/log/common/log' import { ILogService } from 'vs/platform/log/common/log.service' import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider' import { URI } from 'vs/base/common/uri' import { IFileService } from 'vs/platform/files/common/files.service' -import { FileChangeType, FilePermission, FileSystemProviderCapabilities, FileType, IFileSystemProvider, toFileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError, FileSystemProviderErrorCode, IFileChange, IFileDeleteOptions, IFileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability, IFileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files' +import { FileChangeType, FilePermission, FileSystemProviderCapabilities, FileType, IFileSystemProvider, createFileSystemProviderError, FileSystemProviderError, FileSystemProviderErrorCode, IFileChange, IFileDeleteOptions, IFileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability, IFileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files' import { DisposableStore, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle' import { extUri, joinPath } from 'vs/base/common/resources' import { Emitter, Event } from 'vs/base/common/event' @@ -16,7 +16,6 @@ import { IndexedDBFileSystemProvider, IndexedDBFileSystemProviderErrorData, Inde import { IndexedDB } from 'vs/base/browser/indexedDB' import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry.service' import { BufferLogger } from 'vs/platform/log/common/bufferLog' -import { localizeWithPath } from 'vs/nls' import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles.service' import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/browserTextFileService' import { FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService' @@ -414,58 +413,11 @@ class OverlayFileSystemProvider implements IFileSystemProviderWithFileReadWriteC } } -function resourceForError (resource: URI): string { - if (resource.scheme === Schemas.file) { - return resource.fsPath - } - - return resource.toString(true) -} -async function mkdirp (provider: IFileSystemProviderWithFileReadWriteCapability, directory: URI) { - const directoriesToCreate: string[] = [] - - // mkdir until we reach root - while (!extUri.isEqual(directory, extUri.dirname(directory))) { - try { - const stat = await provider.stat(directory) - if ((stat.type & FileType.Directory) === 0) { - throw new Error(localizeWithPath('mkdirExistsError', "Unable to create folder '{0}' that already exists but is not a directory", resourceForError(directory))) - } - - break // we have hit a directory that exists -> good - } catch (error) { - // Bubble up any other error that is not file not found - if (toFileSystemProviderErrorCode(error as Error) !== FileSystemProviderErrorCode.FileNotFound) { - throw error - } - - // Upon error, remember directories that need to be created - directoriesToCreate.push(extUri.basename(directory)) - - // Continue up - directory = extUri.dirname(directory) - } - } - - // Create directories as needed - for (let i = directoriesToCreate.length - 1; i >= 0; i--) { - directory = extUri.joinPath(directory, directoriesToCreate[i]!) - - try { - await provider.mkdir(directory) - } catch (error) { - if (toFileSystemProviderErrorCode(error as Error) !== FileSystemProviderErrorCode.FileExists) { - throw error - } - } - } -} - class MkdirpOnWriteInMemoryFileSystemProvider extends InMemoryFileSystemProvider { override async writeFile (resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { // when using overlay providers, the fileservice won't be able to detect that the parent directory doesn't exist // if another provider has this directory. So it won't create the parent directories on this memory file system. - await mkdirp(this, extUri.dirname(resource)) + await mkdirp(extUri, this, extUri.dirname(resource)) return super.writeFile(resource, content, opts) } diff --git a/src/service-override/localization.ts b/src/service-override/localization.ts index b846e2a9..181af1b0 100644 --- a/src/service-override/localization.ts +++ b/src/service-override/localization.ts @@ -1,14 +1,14 @@ import { IEditorOverrideServices } from 'vs/editor/standalone/browser/standaloneServices' import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' -import { ILanguagePackItem } from 'vs/platform/languagePacks/common/languagePacks' +import { ILanguagePackItem, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks' import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks.service' import { ILocaleService } from 'vs/workbench/services/localization/common/locale.service' import { IDialogService } from 'vs/platform/dialogs/common/dialogs.service' import { IHostService } from 'vs/workbench/services/host/browser/host.service' import { IProductService } from 'vs/platform/product/common/productService.service' -import { localize, localizeWithPath } from 'vs/nls' -import { Language, language } from 'vs/base/common/platform' import { URI } from 'vs/base/common/uri' +import { AbstractLocaleService } from 'vs/workbench/services/localization/browser/localeService' +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement.service' import { getBuiltInExtensionTranslationsUris, setAvailableLocales } from '../l10n' import 'vs/workbench/contrib/localization/browser/localization.contribution' @@ -23,85 +23,41 @@ export interface LocalizationOptions { availableLanguages: AvailableLanguage[] } -class LocaleService implements ILocaleService { - _serviceBrand: undefined - +class LocaleService extends AbstractLocaleService { constructor ( private options: LocalizationOptions, - @IDialogService private readonly dialogService: IDialogService, - @IHostService private readonly hostService: IHostService, - @IProductService private readonly productService: IProductService + @IDialogService dialogService: IDialogService, + @IHostService hostService: IHostService, + @IProductService productService: IProductService ) { + super(dialogService, hostService, productService) } - async setLocale (languagePackItem: ILanguagePackItem): Promise { - const locale = languagePackItem.id - - if (locale === Language.value() || (locale == null && Language.value() === navigator.language.toLowerCase())) { - return - } - + override async storeLocale (locale: string | undefined): Promise { if (locale == null) { await this.options.clearLocale() } else { await this.options.setLocale(locale) } - - const restartDialog = await this.dialogService.confirm({ - type: 'info', - message: localizeWithPath('vs/workbench/services/localization/browser/localeService', 'relaunchDisplayLanguageMessage', 'To change the display language, {0} needs to reload', this.productService.nameLong), - detail: localizeWithPath('vs/workbench/services/localization/browser/localeService', 'relaunchDisplayLanguageDetail', 'Press the reload button to refresh the page and set the display language to {0}.', languagePackItem.label), - primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, '&&Reload') - }) - - if (restartDialog.confirmed) { - await this.hostService.restart() - } } - async clearLocalePreference (): Promise { + override async clearLocale (): Promise { await this.options.clearLocale() - - const restartDialog = await this.dialogService.confirm({ - type: 'info', - message: localizeWithPath('vs/workbench/services/localization/browser/localeService', 'clearDisplayLanguageMessage', 'To change the display language, {0} needs to reload', this.productService.nameLong), - detail: localizeWithPath('vs/workbench/services/localization/browser/localeService', 'clearDisplayLanguageDetail', "Press the reload button to refresh the page and use your browser's language."), - primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, '&&Reload') - }) - - if (restartDialog.confirmed) { - await this.hostService.restart() - } } } -class LanguagePackService implements ILanguagePackService { - _serviceBrand: undefined - +class LanguagePackService extends LanguagePackBaseService implements ILanguagePackService { constructor ( - private options: LocalizationOptions + private options: LocalizationOptions, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService ) { + super(extensionGalleryService) setAvailableLocales(new Set(options.availableLanguages.map(lang => lang.locale))) } - async getAvailableLanguages (): Promise { + override async getAvailableLanguages (): Promise { return this.options.availableLanguages.map(({ locale, languageName }) => { - const label = languageName ?? locale - let description: string | undefined - if (label !== locale) { - description = `(${locale})` - } - - if (locale.toLowerCase() === language.toLowerCase()) { - description ??= '' - description += localizeWithPath('vs/platform/languagePacks/common/languagePacks', 'currentDisplayLanguage', ' (Current)') - } - - return { - id: locale, - label, - description - } + return this.createQuickPickItem(locale, languageName) }) } diff --git a/src/tools/l10n.ts b/src/tools/l10n.ts deleted file mode 100644 index 5b2dec5e..00000000 --- a/src/tools/l10n.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { localize } from 'vs/nls' -import { ParseError, getNodeType, parse } from 'vs/base/common/json' -import { joinPath } from 'vs/base/common/resources' -import { URI } from 'vs/base/common/uri' -import { IExtensionManifest } from 'vs/platform/extensions/common/extensions' -import { IFileService } from 'vs/platform/files/common/files.service' -import { ILogService } from 'vs/platform/log/common/log.service' -import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls' -import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages' -import * as platform from 'vs/base/common/platform' -import { getBuiltInExtensionTranslationsUris } from '../l10n' - -/** - * The code below is extracted from vs/platform/extensionManagement/common/extensionsScannerService.ts - */ -interface Translations { - [id: string]: string -} -type NlsConfiguration = { - language: string | undefined - pseudo: boolean - devMode: boolean - translations: Translations -} -interface MessageBag { - [key: string]: string | { message: string, comment: string[] } -} -interface TranslationBundle { - contents?: { - package: MessageBag - } -} -interface LocalizedMessages { - values: MessageBag | undefined - default: URI | null -} - -export class ExtensionManifestTranslator { - constructor ( - @IFileService protected readonly fileService: IFileService, - @ILogService protected readonly logService: ILogService - ) { - } - - /** - * Parses original message bundle, returns null if the original message bundle is null. - */ - private async resolveOriginalMessageBundle (originalMessageBundle: URI | null, errors: ParseError[]): Promise<{ [key: string]: string } | undefined> { - if (originalMessageBundle != null) { - try { - const originalBundleContent = (await this.fileService.readFile(originalMessageBundle)).value.toString() - return parse(originalBundleContent, errors) - } catch (error) { - /* Ignore Error */ - } - } - return undefined - } - - private findMessageBundles (extensionLocation: URI, nlsConfiguration: NlsConfiguration): Promise<{ localized: URI, original: URI | null }> { - return new Promise<{ localized: URI, original: URI | null }>((resolve) => { - const loop = (locale: string): void => { - const toCheck = joinPath(extensionLocation, `package.nls.${locale}.json`) - void this.fileService.exists(toCheck).then(exists => { - if (exists) { - resolve({ localized: toCheck, original: joinPath(extensionLocation, 'package.nls.json') }) - } - const index = locale.lastIndexOf('-') - if (index === -1) { - resolve({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null }) - } else { - locale = locale.substring(0, index) - loop(locale) - } - }) - } - if (nlsConfiguration.devMode || nlsConfiguration.pseudo || nlsConfiguration.language == null) { - return resolve({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null }) - } - loop(nlsConfiguration.language) - }) - } - - private formatMessage (extensionLocation: URI, message: string): string { - return `[${extensionLocation.path}]: ${message}` - } - - public async translateManifest (extensionLocation: URI, extensionManifest: IExtensionManifest): Promise { - const nlsConfiguration: NlsConfiguration = { - devMode: false, - language: platform.language, - pseudo: platform.language === 'pseudo', - translations: getBuiltInExtensionTranslationsUris(platform.language) ?? {} - } - - const localizedMessages = await this.getLocalizedMessages(extensionLocation, extensionManifest, nlsConfiguration) - if (localizedMessages != null) { - try { - const errors: ParseError[] = [] - // resolveOriginalMessageBundle returns null if localizedMessages.default === undefined; - const defaults = await this.resolveOriginalMessageBundle(localizedMessages.default, errors) - if (errors.length > 0) { - errors.forEach((error) => { - this.logService.error(this.formatMessage(extensionLocation, localize('jsonsParseReportErrors', 'Failed to parse {0}: {1}.', localizedMessages.default?.path, getParseErrorMessage(error.error)))) - }) - return extensionManifest - } else if (getNodeType(localizedMessages) !== 'object') { - this.logService.error(this.formatMessage(extensionLocation, localize('jsonInvalidFormat', 'Invalid format {0}: JSON object expected.', localizedMessages.default?.path))) - return extensionManifest - } - const localized = localizedMessages.values ?? Object.create(null) - return localizeManifest(this.logService, extensionManifest, localized, defaults) - } catch (error) { - /* Ignore Error */ - } - } - return extensionManifest - } - - private async getLocalizedMessages (extensionLocation: URI, extensionManifest: IExtensionManifest, nlsConfiguration: NlsConfiguration): Promise { - const defaultPackageNLS = joinPath(extensionLocation, 'package.nls.json') - const reportErrors = (localized: URI | null, errors: ParseError[]): void => { - errors.forEach((error) => { - this.logService.error(this.formatMessage(extensionLocation, localize('jsonsParseReportErrors', 'Failed to parse {0}: {1}.', localized?.path, getParseErrorMessage(error.error)))) - }) - } - const reportInvalidFormat = (localized: URI | null): void => { - this.logService.error(this.formatMessage(extensionLocation, localize('jsonInvalidFormat', 'Invalid format {0}: JSON object expected.', localized?.path))) - } - - const translationId = `${extensionManifest.publisher}.${extensionManifest.name}` - const translationUri = nlsConfiguration.translations[translationId] - - if (translationUri != null) { - try { - const translationResource = URI.parse(translationUri) - const content = (await this.fileService.readFile(translationResource)).value.toString() - const errors: ParseError[] = [] - const translationBundle: TranslationBundle = parse(content, errors) - if (errors.length > 0) { - reportErrors(translationResource, errors) - return { values: undefined, default: defaultPackageNLS } - } else if (getNodeType(translationBundle) !== 'object') { - reportInvalidFormat(translationResource) - return { values: undefined, default: defaultPackageNLS } - } else { - const values = translationBundle.contents?.package - return { values, default: defaultPackageNLS } - } - } catch (error) { - return { values: undefined, default: defaultPackageNLS } - } - } else { - const exists = await this.fileService.exists(defaultPackageNLS) - if (!exists) { - return undefined - } - let messageBundle - try { - messageBundle = await this.findMessageBundles(extensionLocation, nlsConfiguration) - } catch (error) { - return undefined - } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (messageBundle.localized == null) { - return { values: undefined, default: messageBundle.original } - } - try { - const messageBundleContent = (await this.fileService.readFile(messageBundle.localized)).value.toString() - const errors: ParseError[] = [] - const messages: MessageBag = parse(messageBundleContent, errors) - if (errors.length > 0) { - reportErrors(messageBundle.localized, errors) - return { values: undefined, default: messageBundle.original } - } else if (getNodeType(messages) !== 'object') { - reportInvalidFormat(messageBundle.localized) - return { values: undefined, default: messageBundle.original } - } - return { values: messages, default: messageBundle.original } - } catch (error) { - return { values: undefined, default: messageBundle.original } - } - } - } -} diff --git a/vscode-paches/0057-refactor-split-code-to-be-able-to-import-only-requir.patch b/vscode-paches/0057-refactor-split-code-to-be-able-to-import-only-requir.patch new file mode 100644 index 00000000..f84641f8 --- /dev/null +++ b/vscode-paches/0057-refactor-split-code-to-be-able-to-import-only-requir.patch @@ -0,0 +1,546 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= +Date: Mon, 29 Apr 2024 17:16:21 +0200 +Subject: [PATCH] refactor: split code to be able to import only required part + +--- + .../common/extensionsScannerService.ts | 294 +++++++++--------- + src/vs/platform/files/common/fileService.ts | 111 ++++--- + .../localization/browser/localeService.ts | 37 ++- + 3 files changed, 236 insertions(+), 206 deletions(-) + +diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +index 0b64c8b9b33..484316edbae 100644 +--- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts ++++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +@@ -544,23 +544,169 @@ export class ExtensionScannerInput { + } + } + +-type NlsConfiguration = { ++export type NlsConfiguration = { + language: string | undefined; + pseudo: boolean; + devMode: boolean; + translations: Translations; + }; + +-class ExtensionsScanner extends Disposable { ++export class ExtensionManifestTranslator extends Disposable { ++ constructor( ++ @IFileService protected readonly fileService: IFileService, ++ @ILogService protected readonly logService: ILogService ++ ) { ++ super(); ++ } ++ ++ private async getLocalizedMessages(extensionLocation: URI, extensionManifest: IExtensionManifest, nlsConfiguration: NlsConfiguration): Promise { ++ const defaultPackageNLS = joinPath(extensionLocation, 'package.nls.json'); ++ const reportErrors = (localized: URI | null, errors: ParseError[]): void => { ++ errors.forEach((error) => { ++ this.logService.error(this.formatMessage(extensionLocation, localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localized?.path, getParseErrorMessage(error.error)))); ++ }); ++ }; ++ const reportInvalidFormat = (localized: URI | null): void => { ++ this.logService.error(this.formatMessage(extensionLocation, localize('jsonInvalidFormat', "Invalid format {0}: JSON object expected.", localized?.path))); ++ }; ++ ++ const translationId = `${extensionManifest.publisher}.${extensionManifest.name}`; ++ const translationPath = nlsConfiguration.translations[translationId]; ++ ++ if (translationPath) { ++ try { ++ const translationResource = URI.parse(translationPath); ++ const content = (await this.fileService.readFile(translationResource)).value.toString(); ++ const errors: ParseError[] = []; ++ const translationBundle: TranslationBundle = parse(content, errors); ++ if (errors.length > 0) { ++ reportErrors(translationResource, errors); ++ return { values: undefined, default: defaultPackageNLS }; ++ } else if (getNodeType(translationBundle) !== 'object') { ++ reportInvalidFormat(translationResource); ++ return { values: undefined, default: defaultPackageNLS }; ++ } else { ++ const values = translationBundle.contents ? translationBundle.contents.package : undefined; ++ return { values: values, default: defaultPackageNLS }; ++ } ++ } catch (error) { ++ return { values: undefined, default: defaultPackageNLS }; ++ } ++ } else { ++ const exists = await this.fileService.exists(defaultPackageNLS); ++ if (!exists) { ++ return undefined; ++ } ++ let messageBundle; ++ try { ++ messageBundle = await this.findMessageBundles(extensionLocation, nlsConfiguration); ++ } catch (error) { ++ return undefined; ++ } ++ if (!messageBundle.localized) { ++ return { values: undefined, default: messageBundle.original }; ++ } ++ try { ++ const messageBundleContent = (await this.fileService.readFile(messageBundle.localized)).value.toString(); ++ const errors: ParseError[] = []; ++ const messages: MessageBag = parse(messageBundleContent, errors); ++ if (errors.length > 0) { ++ reportErrors(messageBundle.localized, errors); ++ return { values: undefined, default: messageBundle.original }; ++ } else if (getNodeType(messages) !== 'object') { ++ reportInvalidFormat(messageBundle.localized); ++ return { values: undefined, default: messageBundle.original }; ++ } ++ return { values: messages, default: messageBundle.original }; ++ } catch (error) { ++ return { values: undefined, default: messageBundle.original }; ++ } ++ } ++ } ++ ++ public async translateManifest(extensionLocation: URI, extensionManifest: IExtensionManifest, nlsConfiguration: NlsConfiguration): Promise { ++ const localizedMessages = await this.getLocalizedMessages(extensionLocation, extensionManifest, nlsConfiguration); ++ if (localizedMessages) { ++ try { ++ const errors: ParseError[] = []; ++ // resolveOriginalMessageBundle returns null if localizedMessages.default === undefined; ++ const defaults = await this.resolveOriginalMessageBundle(localizedMessages.default, errors); ++ if (errors.length > 0) { ++ errors.forEach((error) => { ++ this.logService.error(this.formatMessage(extensionLocation, localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localizedMessages.default?.path, getParseErrorMessage(error.error)))); ++ }); ++ return extensionManifest; ++ } else if (getNodeType(localizedMessages) !== 'object') { ++ this.logService.error(this.formatMessage(extensionLocation, localize('jsonInvalidFormat', "Invalid format {0}: JSON object expected.", localizedMessages.default?.path))); ++ return extensionManifest; ++ } ++ const localized = localizedMessages.values || Object.create(null); ++ return localizeManifest(this.logService, extensionManifest, localized, defaults); ++ } catch (error) { ++ /*Ignore Error*/ ++ } ++ } ++ return extensionManifest; ++ } ++ ++ /** ++ * Parses original message bundle, returns null if the original message bundle is null. ++ */ ++ private async resolveOriginalMessageBundle(originalMessageBundle: URI | null, errors: ParseError[]): Promise<{ [key: string]: string } | undefined> { ++ if (originalMessageBundle) { ++ try { ++ const originalBundleContent = (await this.fileService.readFile(originalMessageBundle)).value.toString(); ++ return parse(originalBundleContent, errors); ++ } catch (error) { ++ /* Ignore Error */ ++ } ++ } ++ return; ++ } ++ ++ /** ++ * Finds localized message bundle and the original (unlocalized) one. ++ * If the localized file is not present, returns null for the original and marks original as localized. ++ */ ++ private findMessageBundles(extensionLocation: URI, nlsConfiguration: NlsConfiguration): Promise<{ localized: URI; original: URI | null }> { ++ return new Promise<{ localized: URI; original: URI | null }>((c, e) => { ++ const loop = (locale: string): void => { ++ const toCheck = joinPath(extensionLocation, `package.nls.${locale}.json`); ++ this.fileService.exists(toCheck).then(exists => { ++ if (exists) { ++ c({ localized: toCheck, original: joinPath(extensionLocation, 'package.nls.json') }); ++ } ++ const index = locale.lastIndexOf('-'); ++ if (index === -1) { ++ c({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null }); ++ } else { ++ locale = locale.substring(0, index); ++ loop(locale); ++ } ++ }); ++ }; ++ if (nlsConfiguration.devMode || nlsConfiguration.pseudo || !nlsConfiguration.language) { ++ return c({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null }); ++ } ++ loop(nlsConfiguration.language); ++ }); ++ } ++ ++ protected formatMessage(extensionLocation: URI, message: string): string { ++ return `[${extensionLocation.path}]: ${message}`; ++ } ++} ++ ++class ExtensionsScanner extends ExtensionManifestTranslator { + + constructor( + private readonly obsoleteFile: URI, + @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, +- @IFileService protected readonly fileService: IFileService, +- @ILogService protected readonly logService: ILogService ++ @IFileService fileService: IFileService, ++ @ILogService logService: ILogService + ) { +- super(); ++ super(fileService, logService); + } + + async scanExtensions(input: ExtensionScannerInput): Promise { +@@ -716,144 +862,6 @@ class ExtensionsScanner extends Disposable { + } + return manifest; + } +- +- private async translateManifest(extensionLocation: URI, extensionManifest: IExtensionManifest, nlsConfiguration: NlsConfiguration): Promise { +- const localizedMessages = await this.getLocalizedMessages(extensionLocation, extensionManifest, nlsConfiguration); +- if (localizedMessages) { +- try { +- const errors: ParseError[] = []; +- // resolveOriginalMessageBundle returns null if localizedMessages.default === undefined; +- const defaults = await this.resolveOriginalMessageBundle(localizedMessages.default, errors); +- if (errors.length > 0) { +- errors.forEach((error) => { +- this.logService.error(this.formatMessage(extensionLocation, localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localizedMessages.default?.path, getParseErrorMessage(error.error)))); +- }); +- return extensionManifest; +- } else if (getNodeType(localizedMessages) !== 'object') { +- this.logService.error(this.formatMessage(extensionLocation, localize('jsonInvalidFormat', "Invalid format {0}: JSON object expected.", localizedMessages.default?.path))); +- return extensionManifest; +- } +- const localized = localizedMessages.values || Object.create(null); +- return localizeManifest(this.logService, extensionManifest, localized, defaults); +- } catch (error) { +- /*Ignore Error*/ +- } +- } +- return extensionManifest; +- } +- +- private async getLocalizedMessages(extensionLocation: URI, extensionManifest: IExtensionManifest, nlsConfiguration: NlsConfiguration): Promise { +- const defaultPackageNLS = joinPath(extensionLocation, 'package.nls.json'); +- const reportErrors = (localized: URI | null, errors: ParseError[]): void => { +- errors.forEach((error) => { +- this.logService.error(this.formatMessage(extensionLocation, localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localized?.path, getParseErrorMessage(error.error)))); +- }); +- }; +- const reportInvalidFormat = (localized: URI | null): void => { +- this.logService.error(this.formatMessage(extensionLocation, localize('jsonInvalidFormat', "Invalid format {0}: JSON object expected.", localized?.path))); +- }; +- +- const translationId = `${extensionManifest.publisher}.${extensionManifest.name}`; +- const translationPath = nlsConfiguration.translations[translationId]; +- +- if (translationPath) { +- try { +- const translationResource = URI.file(translationPath); +- const content = (await this.fileService.readFile(translationResource)).value.toString(); +- const errors: ParseError[] = []; +- const translationBundle: TranslationBundle = parse(content, errors); +- if (errors.length > 0) { +- reportErrors(translationResource, errors); +- return { values: undefined, default: defaultPackageNLS }; +- } else if (getNodeType(translationBundle) !== 'object') { +- reportInvalidFormat(translationResource); +- return { values: undefined, default: defaultPackageNLS }; +- } else { +- const values = translationBundle.contents ? translationBundle.contents.package : undefined; +- return { values: values, default: defaultPackageNLS }; +- } +- } catch (error) { +- return { values: undefined, default: defaultPackageNLS }; +- } +- } else { +- const exists = await this.fileService.exists(defaultPackageNLS); +- if (!exists) { +- return undefined; +- } +- let messageBundle; +- try { +- messageBundle = await this.findMessageBundles(extensionLocation, nlsConfiguration); +- } catch (error) { +- return undefined; +- } +- if (!messageBundle.localized) { +- return { values: undefined, default: messageBundle.original }; +- } +- try { +- const messageBundleContent = (await this.fileService.readFile(messageBundle.localized)).value.toString(); +- const errors: ParseError[] = []; +- const messages: MessageBag = parse(messageBundleContent, errors); +- if (errors.length > 0) { +- reportErrors(messageBundle.localized, errors); +- return { values: undefined, default: messageBundle.original }; +- } else if (getNodeType(messages) !== 'object') { +- reportInvalidFormat(messageBundle.localized); +- return { values: undefined, default: messageBundle.original }; +- } +- return { values: messages, default: messageBundle.original }; +- } catch (error) { +- return { values: undefined, default: messageBundle.original }; +- } +- } +- } +- +- /** +- * Parses original message bundle, returns null if the original message bundle is null. +- */ +- private async resolveOriginalMessageBundle(originalMessageBundle: URI | null, errors: ParseError[]): Promise<{ [key: string]: string } | undefined> { +- if (originalMessageBundle) { +- try { +- const originalBundleContent = (await this.fileService.readFile(originalMessageBundle)).value.toString(); +- return parse(originalBundleContent, errors); +- } catch (error) { +- /* Ignore Error */ +- } +- } +- return; +- } +- +- /** +- * Finds localized message bundle and the original (unlocalized) one. +- * If the localized file is not present, returns null for the original and marks original as localized. +- */ +- private findMessageBundles(extensionLocation: URI, nlsConfiguration: NlsConfiguration): Promise<{ localized: URI; original: URI | null }> { +- return new Promise<{ localized: URI; original: URI | null }>((c, e) => { +- const loop = (locale: string): void => { +- const toCheck = joinPath(extensionLocation, `package.nls.${locale}.json`); +- this.fileService.exists(toCheck).then(exists => { +- if (exists) { +- c({ localized: toCheck, original: joinPath(extensionLocation, 'package.nls.json') }); +- } +- const index = locale.lastIndexOf('-'); +- if (index === -1) { +- c({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null }); +- } else { +- locale = locale.substring(0, index); +- loop(locale); +- } +- }); +- }; +- if (nlsConfiguration.devMode || nlsConfiguration.pseudo || !nlsConfiguration.language) { +- return c({ localized: joinPath(extensionLocation, 'package.nls.json'), original: null }); +- } +- loop(nlsConfiguration.language); +- }); +- } +- +- private formatMessage(extensionLocation: URI, message: string): string { +- return `[${extensionLocation.path}]: ${message}`; +- } +- + } + + interface IExtensionCacheData { +diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts +index b353968540e..55977ded402 100644 +--- a/src/vs/platform/files/common/fileService.ts ++++ b/src/vs/platform/files/common/fileService.ts +@@ -23,6 +23,64 @@ import { readFileIntoStream } from 'vs/platform/files/common/io'; + import { ILogService } from 'vs/platform/log/common/log'; + import { ErrorNoTelemetry } from 'vs/base/common/errors'; + ++ ++function resourceForError(resource: URI): string { ++ if (resource.scheme === Schemas.file) { ++ return resource.fsPath; ++ } ++ ++ return resource.toString(true); ++} ++ ++export async function mkdirp(providerExtUri: IExtUri, provider: IFileSystemProvider, directory: URI): Promise { ++ const directoriesToCreate: string[] = []; ++ ++ // mkdir until we reach root ++ while (!providerExtUri.isEqual(directory, providerExtUri.dirname(directory))) { ++ try { ++ const stat = await provider.stat(directory); ++ if ((stat.type & FileType.Directory) === 0) { ++ throw new Error(localize('mkdirExistsError', "Unable to create folder '{0}' that already exists but is not a directory", resourceForError(directory))); ++ } ++ ++ break; // we have hit a directory that exists -> good ++ } catch (error) { ++ ++ // Bubble up any other error that is not file not found ++ if (toFileSystemProviderErrorCode(error) !== FileSystemProviderErrorCode.FileNotFound) { ++ throw error; ++ } ++ ++ // Upon error, remember directories that need to be created ++ directoriesToCreate.push(providerExtUri.basename(directory)); ++ ++ // Continue up ++ directory = providerExtUri.dirname(directory); ++ } ++ } ++ ++ // Create directories as needed ++ for (let i = directoriesToCreate.length - 1; i >= 0; i--) { ++ directory = providerExtUri.joinPath(directory, directoriesToCreate[i]); ++ ++ try { ++ await provider.mkdir(directory); ++ } catch (error) { ++ if (toFileSystemProviderErrorCode(error) !== FileSystemProviderErrorCode.FileExists) { ++ // For mkdirp() we tolerate that the mkdir() call fails ++ // in case the folder already exists. This follows node.js ++ // own implementation of fs.mkdir({ recursive: true }) and ++ // reduces the chances of race conditions leading to errors ++ // if multiple calls try to create the same folders ++ // As such, we only throw an error here if it is other than ++ // the fact that the file already exists. ++ // (see also https://github.com/microsoft/vscode/issues/89834) ++ throw error; ++ } ++ } ++ } ++} ++ + export class FileService extends Disposable implements IFileService { + + declare readonly _serviceBrand: undefined; +@@ -937,53 +995,8 @@ export class FileService extends Disposable implements IFileService { + } + + private async mkdirp(provider: IFileSystemProvider, directory: URI): Promise { +- const directoriesToCreate: string[] = []; +- +- // mkdir until we reach root + const { providerExtUri } = this.getExtUri(provider); +- while (!providerExtUri.isEqual(directory, providerExtUri.dirname(directory))) { +- try { +- const stat = await provider.stat(directory); +- if ((stat.type & FileType.Directory) === 0) { +- throw new Error(localize('mkdirExistsError', "Unable to create folder '{0}' that already exists but is not a directory", this.resourceForError(directory))); +- } +- +- break; // we have hit a directory that exists -> good +- } catch (error) { +- +- // Bubble up any other error that is not file not found +- if (toFileSystemProviderErrorCode(error) !== FileSystemProviderErrorCode.FileNotFound) { +- throw error; +- } +- +- // Upon error, remember directories that need to be created +- directoriesToCreate.push(providerExtUri.basename(directory)); +- +- // Continue up +- directory = providerExtUri.dirname(directory); +- } +- } +- +- // Create directories as needed +- for (let i = directoriesToCreate.length - 1; i >= 0; i--) { +- directory = providerExtUri.joinPath(directory, directoriesToCreate[i]); +- +- try { +- await provider.mkdir(directory); +- } catch (error) { +- if (toFileSystemProviderErrorCode(error) !== FileSystemProviderErrorCode.FileExists) { +- // For mkdirp() we tolerate that the mkdir() call fails +- // in case the folder already exists. This follows node.js +- // own implementation of fs.mkdir({ recursive: true }) and +- // reduces the chances of race conditions leading to errors +- // if multiple calls try to create the same folders +- // As such, we only throw an error here if it is other than +- // the fact that the file already exists. +- // (see also https://github.com/microsoft/vscode/issues/89834) +- throw error; +- } +- } +- } ++ return mkdirp(providerExtUri, provider, directory); + } + + async canDelete(resource: URI, options?: Partial): Promise { +@@ -1436,11 +1449,7 @@ export class FileService extends Disposable implements IFileService { + } + + private resourceForError(resource: URI): string { +- if (resource.scheme === Schemas.file) { +- return resource.fsPath; +- } +- +- return resource.toString(true); ++ return resourceForError(resource); + } + + //#endregion +diff --git a/src/vs/workbench/services/localization/browser/localeService.ts b/src/vs/workbench/services/localization/browser/localeService.ts +index ec2aeac2149..9ddc4b2d58f 100644 +--- a/src/vs/workbench/services/localization/browser/localeService.ts ++++ b/src/vs/workbench/services/localization/browser/localeService.ts +@@ -15,7 +15,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; + import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; + import { ILogService } from 'vs/platform/log/common/log'; + +-export class WebLocaleService implements ILocaleService { ++export abstract class AbstractLocaleService implements ILocaleService { + declare readonly _serviceBrand: undefined; + static readonly _LOCAL_STORAGE_EXTENSION_ID_KEY = 'vscode.nls.languagePackExtensionId'; + static readonly _LOCAL_STORAGE_LOCALE_KEY = 'vscode.nls.locale'; +@@ -26,20 +26,15 @@ export class WebLocaleService implements ILocaleService { + @IProductService private readonly productService: IProductService + ) { } + ++ abstract storeLocale(locale: string | undefined, extensionId: string | undefined): Promise; ++ abstract clearLocale(): Promise; ++ + async setLocale(languagePackItem: ILanguagePackItem, _skipDialog = false): Promise { + const locale = languagePackItem.id; + if (locale === Language.value() || (!locale && Language.value() === navigator.language.toLowerCase())) { + return; + } +- if (locale) { +- localStorage.setItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY, locale); +- if (languagePackItem.extensionId) { +- localStorage.setItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY, languagePackItem.extensionId); +- } +- } else { +- localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY); +- localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY); +- } ++ this.storeLocale(locale, languagePackItem.extensionId); + + const restartDialog = await this.dialogService.confirm({ + type: 'info', +@@ -54,8 +49,7 @@ export class WebLocaleService implements ILocaleService { + } + + async clearLocalePreference(): Promise { +- localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY); +- localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY); ++ this.clearLocale(); + + if (Language.value() === navigator.language.toLowerCase()) { + return; +@@ -74,6 +68,25 @@ export class WebLocaleService implements ILocaleService { + } + } + ++export class WebLocaleService extends AbstractLocaleService { ++ override async storeLocale(locale: string | undefined, extensionId: string | undefined): Promise { ++ if (locale) { ++ localStorage.setItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY, locale); ++ if (extensionId) { ++ localStorage.setItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY, extensionId); ++ } ++ } else { ++ localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY); ++ localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY); ++ } ++ } ++ ++ override async clearLocale(): Promise { ++ localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY); ++ localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY); ++ } ++} ++ + export class WebActiveLanguagePackService implements IActiveLanguagePackService { + _serviceBrand: undefined; + From 6062451564aaead3c47a6d5fa401d9d38c8c21d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 30 Apr 2024 14:28:56 +0200 Subject: [PATCH 2/8] refactor: properly import node internals --- rollup/rollup.config.ts | 121 ++++++++++++++++++------------------ src/service-override/log.ts | 3 +- 2 files changed, 61 insertions(+), 63 deletions(-) diff --git a/rollup/rollup.config.ts b/rollup/rollup.config.ts index ff180442..50a55893 100644 --- a/rollup/rollup.config.ts +++ b/rollup/rollup.config.ts @@ -14,14 +14,13 @@ import glob from 'fast-glob' import { paramCase } from 'param-case' import { PackageJson } from 'type-fest' import copy from 'rollup-plugin-copy' -import * as fs from 'fs' -import * as fsPromise from 'fs/promises' -import * as path from 'path' -import { fileURLToPath } from 'url' +import * as fs from 'node:fs' +import * as nodePath from 'node:path' +import { fileURLToPath } from 'node:url' import metadataPlugin from './rollup-metadata-plugin' import pkg from '../package.json' assert { type: 'json' } -const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const __dirname = nodePath.dirname(fileURLToPath(import.meta.url)) const PURE_ANNO = '#__PURE__' const PURE_FUNCTIONS = new Set([ @@ -99,16 +98,16 @@ const EDITOR_API_EXPOSE_MODULES = [ const EXTENSIONS = ['', '.ts', '.js'] -const BASE_DIR = path.resolve(__dirname, '..') -const TSCONFIG = path.resolve(BASE_DIR, 'tsconfig.rollup.json') -const SRC_DIR = path.resolve(BASE_DIR, 'src') -const DIST_DIR = path.resolve(BASE_DIR, 'dist') -const DIST_DIR_MAIN = path.resolve(DIST_DIR, 'main') -const DIST_SERVICE_OVERRIDE_DIR_MAIN = path.resolve(DIST_DIR_MAIN, 'service-override') -const VSCODE_SRC_DIST_DIR = path.resolve(DIST_DIR_MAIN, 'vscode', 'src') -const VSCODE_DIR = path.resolve(BASE_DIR, 'vscode') -const VSCODE_SRC_DIR = path.resolve(VSCODE_DIR, 'src') -const OVERRIDE_PATH = path.resolve(BASE_DIR, 'src/override') +const BASE_DIR = nodePath.resolve(__dirname, '..') +const TSCONFIG = nodePath.resolve(BASE_DIR, 'tsconfig.rollup.json') +const SRC_DIR = nodePath.resolve(BASE_DIR, 'src') +const DIST_DIR = nodePath.resolve(BASE_DIR, 'dist') +const DIST_DIR_MAIN = nodePath.resolve(DIST_DIR, 'main') +const DIST_SERVICE_OVERRIDE_DIR_MAIN = nodePath.resolve(DIST_DIR_MAIN, 'service-override') +const VSCODE_SRC_DIST_DIR = nodePath.resolve(DIST_DIR_MAIN, 'vscode', 'src') +const VSCODE_DIR = nodePath.resolve(BASE_DIR, 'vscode') +const VSCODE_SRC_DIR = nodePath.resolve(VSCODE_DIR, 'src') +const OVERRIDE_PATH = nodePath.resolve(BASE_DIR, 'src/override') function getMemberExpressionPath (node: recast.types.namedTypes.MemberExpression | recast.types.namedTypes.Identifier): string | null { if (node.type === 'MemberExpression') { @@ -298,7 +297,7 @@ function transformVSCodeCode (id: string, code: string) { function resolveVscode (importee: string, importer?: string) { if (importer != null && importee.startsWith('.')) { - importee = path.resolve(path.dirname(importer), importee) + importee = nodePath.resolve(nodePath.dirname(importer), importee) } // import weak so that AbstractTextEditor is not imported just to do an instanceof on it @@ -307,11 +306,11 @@ function resolveVscode (importee: string, importer?: string) { } if (importee.startsWith('vscode/')) { - return resolve(path.relative('vscode', importee), [VSCODE_DIR]) + return resolve(nodePath.relative('vscode', importee), [VSCODE_DIR]) } let vscodeImportPath = importee if (importee.startsWith(VSCODE_SRC_DIR)) { - vscodeImportPath = path.relative(VSCODE_SRC_DIR, importee) + vscodeImportPath = nodePath.relative(VSCODE_SRC_DIR, importee) } const overridePath = resolve(vscodeImportPath, [OVERRIDE_PATH]) if (overridePath != null) { @@ -333,20 +332,20 @@ const input = { l10n: './src/l10n.ts', monaco: './src/monaco.ts', ...Object.fromEntries( - fs.readdirSync(path.resolve(SRC_DIR, 'service-override'), { withFileTypes: true }) + fs.readdirSync(nodePath.resolve(SRC_DIR, 'service-override'), { withFileTypes: true }) .filter(f => f.isFile()) .map(f => f.name) .map(name => [ - `service-override/${path.basename(name, '.ts')}`, + `service-override/${nodePath.basename(name, '.ts')}`, `./src/service-override/${name}` ]) ), ...Object.fromEntries( - fs.readdirSync(path.resolve(SRC_DIR, 'workers'), { withFileTypes: true }) + fs.readdirSync(nodePath.resolve(SRC_DIR, 'workers'), { withFileTypes: true }) .filter(f => f.isFile()) .map(f => f.name) .map(name => [ - `workers/${path.basename(name, '.ts')}`, + `workers/${nodePath.basename(name, '.ts')}`, `./src/workers/${name}` ]) ) @@ -434,7 +433,7 @@ export default (args: Record): rollup.RollupOptions[] => { return undefined } - const content = (await fsPromise.readFile(id)).toString('utf-8') + const content = (await fs.promises.readFile(id)).toString('utf-8') return transformVSCodeCode(id, content) }, transform (code) { @@ -522,7 +521,7 @@ export default (args: Record): rollup.RollupOptions[] => { return null } - const fakePath = path.resolve(VSCODE_SRC_DIR, importee.replace(/\*/, 'all')) + const fakePath = nodePath.resolve(VSCODE_SRC_DIR, importee.replace(/\*/, 'all')) realPaths.set(fakePath, importee) return fakePath }, @@ -534,11 +533,11 @@ export default (args: Record): rollup.RollupOptions[] => { const files = await glob(realPath, { cwd: VSCODE_SRC_DIR }) const fileRefs = await Promise.all(files.map(async file => { - const filePath = path.resolve(VSCODE_SRC_DIR, file) + const filePath = nodePath.resolve(VSCODE_SRC_DIR, file) const ref = this.emitFile({ type: 'asset', - name: path.basename(file), - source: await fsPromise.readFile(filePath) + name: nodePath.basename(file), + source: await fs.promises.readFile(filePath) }) return { file, ref } })) @@ -657,10 +656,10 @@ export default (args: Record): rollup.RollupOptions[] => { { name: 'externalize-service-overrides', resolveId (source, importer) { - const importerDir = path.dirname(path.resolve(DIST_DIR_MAIN, importer ?? '/')) - const resolved = path.resolve(importerDir, source) - if (path.dirname(resolved) === DIST_SERVICE_OVERRIDE_DIR_MAIN && importer != null) { - const serviceOverride = path.basename(resolved, '.js') + const importerDir = nodePath.dirname(nodePath.resolve(DIST_DIR_MAIN, importer ?? '/')) + const resolved = nodePath.resolve(importerDir, source) + if (nodePath.dirname(resolved) === DIST_SERVICE_OVERRIDE_DIR_MAIN && importer != null) { + const serviceOverride = nodePath.basename(resolved, '.js') return { external: true, id: `@codingame/monaco-vscode-${paramCase(serviceOverride)}-service-override` @@ -684,24 +683,24 @@ export default (args: Record): rollup.RollupOptions[] => { metadataPlugin({ // generate package.json and service-override packages getGroup (id: string, options) { - const serviceOverrideDir = path.resolve(options.dir!, 'service-override') - const workersDir = path.resolve(options.dir!, 'workers') + const serviceOverrideDir = nodePath.resolve(options.dir!, 'service-override') + const workersDir = nodePath.resolve(options.dir!, 'workers') if (id.startsWith(serviceOverrideDir)) { - const name = paramCase(path.basename(id, '.js')) + const name = paramCase(nodePath.basename(id, '.js')) return { name: `service-override:${name}`, publicName: `@codingame/monaco-vscode-${name}-service-override` } } if (id.startsWith(workersDir)) { - const name = workerGroups[path.basename(id, '.worker.js')] + const name = workerGroups[nodePath.basename(id, '.worker.js')] return { name: name != null ? `service-override:${name}` : 'main', publicName: name != null ? `@codingame/monaco-vscode-${name}-service-override` : 'vscode' } } - if (id === path.resolve(options.dir!, 'editor.api.js')) { + if (id === nodePath.resolve(options.dir!, 'editor.api.js')) { return { name: 'editor.api', publicName: '@codngame/monaco-vscode-editor-api' @@ -814,9 +813,9 @@ export default (args: Record): rollup.RollupOptions[] => { type: 'asset' }) } else if (group.name === 'editor.api') { - const directory = path.resolve(DIST_DIR, 'editor-api') + const directory = nodePath.resolve(DIST_DIR, 'editor-api') - await fsPromise.mkdir(directory, { + await fs.promises.mkdir(directory, { recursive: true }) @@ -879,15 +878,15 @@ export default (args: Record): rollup.RollupOptions[] => { id: source } } - const importerDir = path.dirname(path.resolve(DIST_DIR_MAIN, importer ?? '/')) - const resolved = path.resolve(importerDir, source) + const importerDir = nodePath.dirname(nodePath.resolve(DIST_DIR_MAIN, importer ?? '/')) + const resolved = nodePath.resolve(importerDir, source) const resolvedWithExtension = resolved.endsWith('.js') ? resolved : `${resolved}.js` const isVscodeFile = resolved.startsWith(VSCODE_SRC_DIST_DIR) - const isServiceOverride = path.dirname(resolved) === DIST_SERVICE_OVERRIDE_DIR_MAIN + const isServiceOverride = nodePath.dirname(resolved) === DIST_SERVICE_OVERRIDE_DIR_MAIN const isExclusive = group.exclusiveModules.has(resolvedWithExtension) - const pathFromRoot = path.relative(DIST_DIR_MAIN, resolvedWithExtension) - const shouldBeShared = SHARED_ROOT_FILES_BETWEEN_PACKAGES.includes(path.relative(DIST_DIR_MAIN, resolvedWithExtension)) + const pathFromRoot = nodePath.relative(DIST_DIR_MAIN, resolvedWithExtension) + const shouldBeShared = SHARED_ROOT_FILES_BETWEEN_PACKAGES.includes(nodePath.relative(DIST_DIR_MAIN, resolvedWithExtension)) if (pathFromRoot.startsWith('external/') && !isExclusive) { return { external: true, @@ -905,7 +904,7 @@ export default (args: Record): rollup.RollupOptions[] => { const importFromGroup = isVscodeFile ? moduleGroupName.get(resolved) ?? 'main' : 'main' const importFromModule = getPackageFromGroupName(importFromGroup) // Those modules will be imported from external monaco-vscode-api - let externalResolved = resolved.startsWith(VSCODE_SRC_DIST_DIR) ? `${importFromModule}/vscode/${path.relative(VSCODE_SRC_DIST_DIR, resolved)}` : `${importFromModule}/${path.relative(DIST_DIR_MAIN, resolved)}` + let externalResolved = resolved.startsWith(VSCODE_SRC_DIST_DIR) ? `${importFromModule}/vscode/${nodePath.relative(VSCODE_SRC_DIST_DIR, resolved)}` : `${importFromModule}/${nodePath.relative(DIST_DIR_MAIN, resolved)}` if (externalResolved.endsWith('.js')) { externalResolved = externalResolved.slice(0, -3) } @@ -922,9 +921,9 @@ export default (args: Record): rollup.RollupOptions[] => { return `export * from '${Array.from(group.entrypoints)[0]!.slice(0, -3)}'` } if (id.startsWith('vscode/')) { - return (bundle[path.relative('vscode', id)] as rollup.OutputChunk | undefined)?.code + return (bundle[nodePath.relative('vscode', id)] as rollup.OutputChunk | undefined)?.code } - return (bundle[path.relative(DIST_DIR_MAIN, id)] as rollup.OutputChunk | undefined)?.code + return (bundle[nodePath.relative(DIST_DIR_MAIN, id)] as rollup.OutputChunk | undefined)?.code }, resolveFileUrl (options) { let relativePath = options.relativePath @@ -974,14 +973,14 @@ export default (args: Record): rollup.RollupOptions[] => { await groupBundle.close() // remove exclusive files from main bundle to prevent them from being duplicated for (const exclusiveModule of group.exclusiveModules) { - delete bundle[path.relative(DIST_DIR_MAIN, exclusiveModule)] + delete bundle[nodePath.relative(DIST_DIR_MAIN, exclusiveModule)] } } else { const [_, category, name] = /^(.*):(.*)$/.exec(group.name)! - const directory = path.resolve(DIST_DIR, `${category}-${name}`) + const directory = nodePath.resolve(DIST_DIR, `${category}-${name}`) - await fsPromise.mkdir(directory, { + await fs.promises.mkdir(directory, { recursive: true }) const serviceOverrideEntryPoint = Array.from(group.entrypoints).find(e => e.includes('/service-override/'))! @@ -1050,15 +1049,15 @@ export default (args: Record): rollup.RollupOptions[] => { id: source } } - const importerDir = path.dirname(path.resolve(DIST_DIR_MAIN, importer ?? '/')) - const resolved = path.resolve(importerDir, source) + const importerDir = nodePath.dirname(nodePath.resolve(DIST_DIR_MAIN, importer ?? '/')) + const resolved = nodePath.resolve(importerDir, source) const resolvedWithExtension = resolved.endsWith('.js') ? resolved : `${resolved}.js` const isVscodeFile = resolved.startsWith(VSCODE_SRC_DIST_DIR) - const isServiceOverride = path.dirname(resolved) === DIST_SERVICE_OVERRIDE_DIR_MAIN + const isServiceOverride = nodePath.dirname(resolved) === DIST_SERVICE_OVERRIDE_DIR_MAIN const isExclusive = group.exclusiveModules.has(resolvedWithExtension) - const pathFromRoot = path.relative(DIST_DIR_MAIN, resolvedWithExtension) - const shouldBeShared = SHARED_ROOT_FILES_BETWEEN_PACKAGES.includes(path.relative(DIST_DIR_MAIN, resolvedWithExtension)) + const pathFromRoot = nodePath.relative(DIST_DIR_MAIN, resolvedWithExtension) + const shouldBeShared = SHARED_ROOT_FILES_BETWEEN_PACKAGES.includes(nodePath.relative(DIST_DIR_MAIN, resolvedWithExtension)) if (pathFromRoot.startsWith('external/') && !isExclusive) { return { external: true, @@ -1077,7 +1076,7 @@ export default (args: Record): rollup.RollupOptions[] => { const importFromGroup = isVscodeFile ? moduleGroupName.get(resolved) ?? 'main' : 'main' const importFromModule = getPackageFromGroupName(importFromGroup) // Those modules will be imported from external monaco-vscode-api - let externalResolved = resolved.startsWith(VSCODE_SRC_DIST_DIR) ? `${importFromModule}/vscode/${path.relative(VSCODE_SRC_DIST_DIR, resolved)}` : `${importFromModule}/${path.relative(DIST_DIR_MAIN, resolved)}` + let externalResolved = resolved.startsWith(VSCODE_SRC_DIST_DIR) ? `${importFromModule}/vscode/${nodePath.relative(VSCODE_SRC_DIST_DIR, resolved)}` : `${importFromModule}/${nodePath.relative(DIST_DIR_MAIN, resolved)}` if (externalResolved.endsWith('.js')) { externalResolved = externalResolved.slice(0, -3) } @@ -1107,9 +1106,9 @@ export default (args: Record): rollup.RollupOptions[] => { return `import '${workerEntryPoint}'` } if (id.startsWith('vscode/')) { - return (bundle[path.relative('vscode', id)] as rollup.OutputChunk | undefined)?.code + return (bundle[nodePath.relative('vscode', id)] as rollup.OutputChunk | undefined)?.code } - return (bundle[path.relative(DIST_DIR_MAIN, id)] as rollup.OutputChunk | undefined)?.code + return (bundle[nodePath.relative(DIST_DIR_MAIN, id)] as rollup.OutputChunk | undefined)?.code }, resolveFileUrl (options) { let relativePath = options.relativePath @@ -1130,7 +1129,7 @@ export default (args: Record): rollup.RollupOptions[] => { }) const output = await groupBundle.write({ preserveModules: true, - preserveModulesRoot: path.resolve(DIST_DIR, 'main/service-override'), + preserveModulesRoot: nodePath.resolve(DIST_DIR, 'main/service-override'), minifyInternalExports: false, assetFileNames: 'assets/[name][extname]', format: 'esm', @@ -1143,7 +1142,7 @@ export default (args: Record): rollup.RollupOptions[] => { // remove exclusive files from main bundle to prevent them from being duplicated for (const exclusiveModule of group.exclusiveModules) { - delete bundle[path.relative(DIST_DIR_MAIN, exclusiveModule)] + delete bundle[nodePath.relative(DIST_DIR_MAIN, exclusiveModule)] } const assets = output.output @@ -1158,7 +1157,7 @@ export default (args: Record): rollup.RollupOptions[] => { name: 'clean-src', async generateBundle () { // Delete intermediate sources before writing to make sure there is no unused files - await fsPromise.rm(DIST_DIR_MAIN, { + await fs.promises.rm(DIST_DIR_MAIN, { recursive: true }) } @@ -1176,7 +1175,7 @@ export default (args: Record): rollup.RollupOptions[] => { function resolve (_path: string, fromPaths: string[]) { for (const fromPath of fromPaths) { for (const extension of EXTENSIONS) { - const outputPath = path.resolve(fromPath, `${_path}${extension}`) + const outputPath = nodePath.resolve(fromPath, `${_path}${extension}`) if (fs.existsSync(outputPath) && fs.lstatSync(outputPath).isFile()) { return outputPath } diff --git a/src/service-override/log.ts b/src/service-override/log.ts index b48b9b12..5bbdab70 100644 --- a/src/service-override/log.ts +++ b/src/service-override/log.ts @@ -6,7 +6,6 @@ import { ILogService, ILoggerService } from 'vs/platform/log/common/log.service' import { FileLoggerService } from 'vs/platform/log/common/fileLog' import { LogService } from 'vs/platform/log/common/logService' import { IEnvironmentService } from 'vs/platform/environment/common/environment.service' -import { localizeWithPath } from 'vs/nls' import { windowLogId } from 'vs/workbench/services/log/common/logConstants' import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService.service' import { IDisposable, toDisposable } from 'vs/base/common/lifecycle' @@ -26,7 +25,7 @@ class _FileLoggerService extends FileLoggerService { const otherLoggers: ILogger[] = [] class _LogService extends LogService { constructor (@ILoggerService loggerService: ILoggerService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService) { - const logger = loggerService.createLogger(environmentService.logFile, { id: windowLogId, name: localizeWithPath('vs/workbench/browser/web.main', 'rendererLog', 'Window') }) + const logger = loggerService.createLogger(environmentService.logFile, { id: windowLogId, name: 'Window' }) super(logger, otherLoggers) } } From 23282737be496c3d7a258213bdb85b1c0e9b968a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 30 Apr 2024 14:29:09 +0200 Subject: [PATCH 3/8] fix: missing contribution --- src/contributions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contributions.ts b/src/contributions.ts index 0d02d037..a07ae679 100644 --- a/src/contributions.ts +++ b/src/contributions.ts @@ -1,6 +1,7 @@ import 'vs/workbench/api/common/jsonValidationExtensionPoint' import 'vs/workbench/services/themes/common/colorExtensionPoint' import 'vs/workbench/services/themes/common/iconExtensionPoint' +import 'vs/platform/actions/common/actions.contribution' // Selectively comes from vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts import 'vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch' import 'vs/workbench/contrib/codeEditor/browser/menuPreventer' From 9c3e18abb9d9ade0a6c085e1c8f6e5cc2ddcd0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 30 Apr 2024 14:58:17 +0200 Subject: [PATCH 4/8] fix: still traverse throw statements --- rollup/rollup.config.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rollup/rollup.config.ts b/rollup/rollup.config.ts index 50a55893..791a4835 100644 --- a/rollup/rollup.config.ts +++ b/rollup/rollup.config.ts @@ -227,10 +227,6 @@ function transformVSCodeCode (id: string, code: string) { } } this.traverse(path) - return undefined - }, - visitThrowStatement () { - return false }, visitClassDeclaration (path) { /** From 4cd8e113918f99b1184e07ca47168c1ee1243c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 30 Apr 2024 14:59:14 +0200 Subject: [PATCH 5/8] fix: do not call localize from our code --- src/override/vs/workbench/browser/web.main.ts | 3 +++ src/service-override/log.ts | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 src/override/vs/workbench/browser/web.main.ts diff --git a/src/override/vs/workbench/browser/web.main.ts b/src/override/vs/workbench/browser/web.main.ts new file mode 100644 index 00000000..0b6f398f --- /dev/null +++ b/src/override/vs/workbench/browser/web.main.ts @@ -0,0 +1,3 @@ +import { localizeWithPath } from 'vs/nls' +const _moduleId = 'vs/workbench/browser/web.main' +export const rendererLogLabel = localizeWithPath(_moduleId, 'rendererLog', 'Window') diff --git a/src/service-override/log.ts b/src/service-override/log.ts index 5bbdab70..5bc729a6 100644 --- a/src/service-override/log.ts +++ b/src/service-override/log.ts @@ -14,6 +14,7 @@ import { IDefaultLogLevelsService } from 'vs/workbench/contrib/logs/common/defau import getEnvironmentServiceOverride from './environment' import { logsPath } from '../workbench' import { checkServicesNotInitialized } from '../lifecycle' +import { rendererLogLabel } from '../override/vs/workbench/browser/web.main' import 'vs/workbench/contrib/logs/common/logs.contribution' class _FileLoggerService extends FileLoggerService { @@ -25,7 +26,7 @@ class _FileLoggerService extends FileLoggerService { const otherLoggers: ILogger[] = [] class _LogService extends LogService { constructor (@ILoggerService loggerService: ILoggerService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService) { - const logger = loggerService.createLogger(environmentService.logFile, { id: windowLogId, name: 'Window' }) + const logger = loggerService.createLogger(environmentService.logFile, { id: windowLogId, name: rendererLogLabel }) super(logger, otherLoggers) } } From 8e190f115e44426ded9eb36939cff8124eb0bec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 30 Apr 2024 14:59:37 +0200 Subject: [PATCH 6/8] fix: only store the module id once --- ...o-register-a-locale-with-translation.patch | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/vscode-paches/0006-feat-add-a-way-to-register-a-locale-with-translation.patch b/vscode-paches/0006-feat-add-a-way-to-register-a-locale-with-translation.patch index c2c87eb4..3535c707 100644 --- a/vscode-paches/0006-feat-add-a-way-to-register-a-locale-with-translation.patch +++ b/vscode-paches/0006-feat-add-a-way-to-register-a-locale-with-translation.patch @@ -4,13 +4,13 @@ Date: Mon, 11 Mar 2024 16:45:29 +0100 Subject: [PATCH] feat: add a way to register a locale with translations --- - build/lib/standalone.js | 12 ++++++++++-- - build/lib/standalone.ts | 8 ++++++++ + build/lib/standalone.js | 16 ++++++++++++++-- + build/lib/standalone.ts | 12 ++++++++++++ src/vs/nls.ts | 31 ++++++++++++++++++++++++++++++- - 3 files changed, 48 insertions(+), 3 deletions(-) + 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/build/lib/standalone.js b/build/lib/standalone.js -index 723a1127492..9d09ee7d9f5 100644 +index 723a1127492..a7072610326 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -4,8 +4,7 @@ @@ -31,21 +31,25 @@ index 723a1127492..9d09ee7d9f5 100644 function createESMSourcesAndResources2(options) { const ts = require('typescript'); const SRC_FOLDER = path.join(REPO_ROOT, options.srcFolder); -@@ -190,6 +190,13 @@ function createESMSourcesAndResources2(options) { +@@ -190,6 +190,17 @@ function createESMSourcesAndResources2(options) { fileContents = fileContents.replace(/import ([a-zA-Z0-9]+) = require\(('[^']+')\);/g, function (_, m1, m2) { return `import * as ${m1} from ${m2};`; }); + if (!file.includes('vs/nls')) { -+ fileContents = fileContents.replace(/\b(localize2?)\(/g, function (_, name) { -+ return `${name}WithPath('${file.slice(0, -3)}', `; ++ let newFileContents = fileContents.replace(/\b(localize2?)\(/g, function (_, name) { ++ return `${name}WithPath(_moduleId, `; + }); ++ if (newFileContents !== fileContents) { ++ newFileContents = `//@ts-ignore\nconst _moduleId = "${file.slice(0, -3)}"\n${newFileContents}`; ++ } ++ fileContents = newFileContents; + } + fileContents = fileContents.replace(/import { (localize2?) }/g, 'import { $1WithPath }'); + fileContents = fileContents.replace(/import { (localize2?), *(localize2?) }/g, 'import { $1WithPath, $2WithPath }'); write(getDestAbsoluteFilePath(file), fileContents); continue; } -@@ -258,6 +265,7 @@ function createESMSourcesAndResources2(options) { +@@ -258,6 +269,7 @@ function createESMSourcesAndResources2(options) { } } } @@ -54,17 +58,21 @@ index 723a1127492..9d09ee7d9f5 100644 if (!/\.css/.test(module)) { return false; diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts -index 90517a9236f..8095b353bb4 100644 +index 90517a9236f..7e074315308 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts -@@ -223,6 +223,14 @@ export function createESMSourcesAndResources2(options: IOptions2): void { +@@ -223,6 +223,18 @@ export function createESMSourcesAndResources2(options: IOptions2): void { return `import * as ${m1} from ${m2};`; }); + if (!file.includes('vs/nls')) { -+ fileContents = fileContents.replace(/\b(localize2?)\(/g, function (_, name) { -+ return `${name}WithPath('${file.slice(0, -3)}', `; ++ let newFileContents = fileContents.replace(/\b(localize2?)\(/g, function (_, name) { ++ return `${name}WithPath(_moduleId, `; + }); ++ if (newFileContents !== fileContents) { ++ newFileContents = `//@ts-ignore\nconst _moduleId = "${file.slice(0, -3)}"\n${newFileContents}`; ++ } ++ fileContents = newFileContents; + } + fileContents = fileContents.replace(/import { (localize2?) }/g, 'import { $1WithPath }'); + fileContents = fileContents.replace(/import { (localize2?), *(localize2?) }/g, 'import { $1WithPath, $2WithPath }'); From 127e5801a1bd5fcce3e02fd85cbddbab5ede67aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 30 Apr 2024 15:14:18 +0200 Subject: [PATCH 7/8] fix: optimize localization replace localization keys by indexes --- rollup/rollup.config.ts | 47 ++++++++++++++++++++++++++++++--- rollup/rollup.language-packs.ts | 22 +++++++-------- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/rollup/rollup.config.ts b/rollup/rollup.config.ts index 791a4835..633418b8 100644 --- a/rollup/rollup.config.ts +++ b/rollup/rollup.config.ts @@ -158,6 +158,8 @@ function isCallPure (file: string, functionName: string, node: recast.types.name return PURE_OR_TO_REMOVE_FUNCTIONS.has(functionName) } +const moduleNlsKeys: Record = {} + function transformVSCodeCode (id: string, code: string) { // HACK: assign typescript decorator result to a decorated class field so rollup doesn't remove them // before: @@ -206,7 +208,43 @@ function transformVSCodeCode (id: string, code: string) { const node = path.node const name = node.callee.type === 'MemberExpression' || node.callee.type === 'Identifier' ? getMemberExpressionPath(node.callee) : null - if (node.callee.type === 'MemberExpression') { + if (name != null && (name.endsWith('localizeWithPath') || name.endsWith('localize2WithPath'))) { + const translationPath = nodePath.relative(id.startsWith(OVERRIDE_PATH) ? OVERRIDE_PATH : VSCODE_SRC_DIR, id).slice(0, -3) + let localizationKey: string + if (path.node.arguments[1]?.type === 'StringLiteral') { + localizationKey = path.node.arguments[1].value + } else if (path.node.arguments[1]?.type === 'ObjectExpression') { + const properties = path.node.arguments[1].properties + const keyProperty = properties.find((prop): prop is recast.types.namedTypes.ObjectProperty => prop.type === 'ObjectProperty' && prop.key.type === 'Identifier' && prop.key.name === 'key') + if (keyProperty == null) { + throw new Error('No key property') + } + if (keyProperty.value.type !== 'StringLiteral') { + throw new Error('Key property is not literal') + } + localizationKey = keyProperty.value.value + } else if (path.node.arguments[1]?.type === 'TemplateLiteral' && path.node.arguments[1].expressions.length === 0 && path.node.arguments[1].quasis.length === 1) { + localizationKey = path.node.arguments[1].quasis[0]!.value.raw + } else { + throw new Error('Unable to extract translation key') + } + let nlsKeys = moduleNlsKeys[translationPath] + if (nlsKeys == null) { + nlsKeys = [] + moduleNlsKeys[translationPath] = nlsKeys + } + + let index = nlsKeys.indexOf(localizationKey) + if (index < 0) { + index = nlsKeys.length + nlsKeys.push(localizationKey) + } + path.replace(recast.types.builders.callExpression( + path.node.callee, + [path.node.arguments[0]!, recast.types.builders.numericLiteral(index), ...path.node.arguments.slice(2)] + )) + transformed = true + } else if (node.callee.type === 'MemberExpression') { if (node.callee.property.type === 'Identifier') { const names: string[] = [node.callee.property.name] if (name != null) { @@ -422,10 +460,10 @@ export default (args: Record): rollup.RollupOptions[] => { return undefined }, async load (id) { - if (!id.startsWith(VSCODE_SRC_DIR)) { + if (!id.startsWith(VSCODE_SRC_DIR) && !id.startsWith(OVERRIDE_PATH)) { return undefined } - if (!id.endsWith('.js')) { + if (!id.endsWith('.js') && !id.endsWith('.ts')) { return undefined } @@ -434,6 +472,9 @@ export default (args: Record): rollup.RollupOptions[] => { }, transform (code) { return code.replaceAll("'./keyboardLayouts/layout.contribution.' + platform", "'./keyboardLayouts/layout.contribution.' + platform + '.js'") + }, + async writeBundle () { + await fs.promises.writeFile(nodePath.resolve(DIST_DIR, 'nls.metadata.json'), JSON.stringify(moduleNlsKeys, null, 2)) } }, { diff --git a/rollup/rollup.language-packs.ts b/rollup/rollup.language-packs.ts index d347114f..8a25afe0 100644 --- a/rollup/rollup.language-packs.ts +++ b/rollup/rollup.language-packs.ts @@ -3,7 +3,6 @@ import * as rollup from 'rollup' import { IExtensionManifest } from 'vs/platform/extensions/common/extensions' import { dataToEsm } from '@rollup/pluginutils' import { PackageJson } from 'type-fest' -import glob from 'fast-glob' import * as fs from 'fs' import * as path from 'path' import { fileURLToPath } from 'url' @@ -22,12 +21,7 @@ const locExtensions = fs.readdirSync(LOC_PATH, { withFileTypes: true }) .filter(f => f.isDirectory() && fs.existsSync(path.resolve(LOC_PATH, f.name, 'package.json'))) .map(f => f.name) -const vscodeModules = (await glob('**/vscode/src/**/*.js', { - cwd: DIST_DIR, - onlyFiles: true -})).map(fileName => /vscode\/src\/(.*).js$/.exec(fileName)![1]!) - -const usedModules = new Set([...vscodeModules, 'vs/workbench/browser/web.main']) +const nlsMetadata: Record = JSON.parse((await fs.promises.readFile(path.resolve(DIST_DIR, 'nls.metadata.json'))).toString()) export default rollup.defineConfig([ ...locExtensions.map(name => ({ @@ -92,12 +86,18 @@ ${Object.entries(translationAssets).map(([id, assetRef]) => ` '${id}': new URL( transform (code, id) { if (!id.endsWith('.json')) return null - const parsed = JSON.parse(code).contents - // remove keys that we don't use - const filtered = Object.fromEntries(Object.entries(parsed).filter(([key]) => usedModules.has(key))) + const parsed: Record> = JSON.parse(code).contents + + const encoded = Object.fromEntries(Object.entries(nlsMetadata).map(([moduleId, keys]) => { + const values = parsed[moduleId] ?? {} + return [ + moduleId, + keys.map(key => values[key]) + ] + })) return { - code: dataToEsm(filtered, { + code: dataToEsm(encoded, { preferConst: true }), map: { mappings: '' } From 35b66ee27f89605b009ccf4b39ecbf93ab47b6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 30 Apr 2024 15:14:46 +0200 Subject: [PATCH 8/8] fix: do not encode i10l location for all extensions --- ...markdown-do-not-encode-i10l-location.patch | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/vscode-paches/0007-fix-markdown-do-not-encode-i10l-location.patch b/vscode-paches/0007-fix-markdown-do-not-encode-i10l-location.patch index aa594583..74f56583 100644 --- a/vscode-paches/0007-fix-markdown-do-not-encode-i10l-location.patch +++ b/vscode-paches/0007-fix-markdown-do-not-encode-i10l-location.patch @@ -4,9 +4,51 @@ Date: Mon, 11 Mar 2024 16:46:45 +0100 Subject: [PATCH] fix(markdown): do not encode i10l location --- - extensions/markdown-language-features/src/extension.browser.ts | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + .../css-language-features/client/src/browser/cssClientMain.ts | 2 +- + .../html-language-features/client/src/browser/htmlClientMain.ts | 2 +- + .../json-language-features/client/src/browser/jsonClientMain.ts | 2 +- + extensions/markdown-language-features/src/extension.browser.ts | 2 +- + 4 files changed, 4 insertions(+), 4 deletions(-) +diff --git a/extensions/css-language-features/client/src/browser/cssClientMain.ts b/extensions/css-language-features/client/src/browser/cssClientMain.ts +index 6522c786389..ad53b9b350a 100644 +--- a/extensions/css-language-features/client/src/browser/cssClientMain.ts ++++ b/extensions/css-language-features/client/src/browser/cssClientMain.ts +@@ -22,7 +22,7 @@ export async function activate(context: ExtensionContext) { + const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/cssServerMain.js'); + try { + const worker = new Worker(serverMain.toString()); +- worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); ++ worker.postMessage({ i10lLocation: l10n.uri?.toString(true) ?? '' }); + + const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { + return new LanguageClient(id, name, clientOptions, worker); +diff --git a/extensions/html-language-features/client/src/browser/htmlClientMain.ts b/extensions/html-language-features/client/src/browser/htmlClientMain.ts +index 3f10e6d131f..350d27322db 100644 +--- a/extensions/html-language-features/client/src/browser/htmlClientMain.ts ++++ b/extensions/html-language-features/client/src/browser/htmlClientMain.ts +@@ -22,7 +22,7 @@ export async function activate(context: ExtensionContext) { + const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/htmlServerMain.js'); + try { + const worker = new Worker(serverMain.toString()); +- worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); ++ worker.postMessage({ i10lLocation: l10n.uri?.toString(true) ?? '' }); + + const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { + return new LanguageClient(id, name, clientOptions, worker); +diff --git a/extensions/json-language-features/client/src/browser/jsonClientMain.ts b/extensions/json-language-features/client/src/browser/jsonClientMain.ts +index f78f494d727..2def35ef026 100644 +--- a/extensions/json-language-features/client/src/browser/jsonClientMain.ts ++++ b/extensions/json-language-features/client/src/browser/jsonClientMain.ts +@@ -21,7 +21,7 @@ export async function activate(context: ExtensionContext) { + const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/jsonServerMain.js'); + try { + const worker = new Worker(serverMain.toString()); +- worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); ++ worker.postMessage({ i10lLocation: l10n.uri?.toString(true) ?? '' }); + + const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { + return new LanguageClient(id, name, clientOptions, worker); diff --git a/extensions/markdown-language-features/src/extension.browser.ts b/extensions/markdown-language-features/src/extension.browser.ts index 30639672490..2095905a4d8 100644 --- a/extensions/markdown-language-features/src/extension.browser.ts