diff --git a/src/features/Folding.ts b/src/features/Folding.ts index 59f316b927..2a793b00d7 100644 --- a/src/features/Folding.ts +++ b/src/features/Folding.ts @@ -2,6 +2,7 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ +import fs = require("fs"); import * as path from "path"; import * as vscode from "vscode"; import { @@ -9,7 +10,7 @@ import { LanguageClient, } from "vscode-languageclient"; import { IFeature } from "../feature"; -import { Logger } from "../logging"; +import { ILogger } from "../logging"; import * as Settings from "../settings"; /** @@ -497,23 +498,26 @@ export class FoldingFeature implements IFeature { * @param logger The logging object to send messages to * @param documentSelector documentSelector object for this Folding Provider */ - constructor(private logger: Logger, documentSelector: DocumentSelector) { - const grammar: IGrammar = this.grammar(logger); - + constructor(private logger: ILogger, documentSelector: DocumentSelector) { const settings = Settings.load(); if (!(settings.codeFolding && settings.codeFolding.enable)) { return; } - // If the PowerShell grammar is not available for some reason, don't register a folding provider, - // which reverts VSCode to the default indentation style folding - if (grammar == null) { - logger.writeWarning("Unable to load the PowerShell grammar file"); - return; - } + this.loadPSGrammar(logger) + .then((grammar) => { + // If the PowerShell grammar is not available for some reason, don't register a folding provider, + // which reverts VSCode to the default indentation style folding + if (!grammar) { + logger.writeWarning("Unable to load the PowerShell grammar file"); + return; + } - this.foldingProvider = new FoldingProvider(grammar); - vscode.languages.registerFoldingRangeProvider(documentSelector, this.foldingProvider); + this.foldingProvider = new FoldingProvider(grammar); + vscode.languages.registerFoldingRangeProvider(documentSelector, this.foldingProvider); - logger.write("Syntax Folding Provider registered"); + logger.write("Syntax Folding Provider registered"); + }, (err) => { + this.logger.writeError(`Failed to load grammar file - error: ${err}`); + }); } /* dispose() is required by the IFeature interface, but is not required by this feature */ @@ -527,7 +531,7 @@ export class FoldingFeature implements IFeature { * @param logger The logging object to send messages to * @returns A grammar parser for the PowerShell language is succesful or undefined if an error occured */ - public grammar(logger: Logger): IGrammar { + public loadPSGrammar(logger: ILogger): Thenable { const tm = this.getCoreNodeModule("vscode-textmate", logger); if (tm == null) { return undefined; } logger.writeDiagnostic(`Loaded the vscode-textmate module`); @@ -537,10 +541,18 @@ export class FoldingFeature implements IFeature { const grammarPath = this.powerShellGrammarPath(); if (grammarPath == null) { return undefined; } logger.writeDiagnostic(`PowerShell grammar file specified as ${grammarPath}`); - try { - return registry.loadGrammarFromPathSync(grammarPath); - } catch (err) { - logger.writeError(`Error while loading the PowerShell grammar file at ${grammarPath}`, err); + + // Branching for the different vscode-textmate modules + if ("loadGrammarFromPathSync" in registry) { + // V3 of the module allows synchronous loading of a grammar + return new Promise( (grammar) => { + return registry.loadGrammarFromPathSync(grammarPath); + }); + } else { + // However in V4+ this is async only + const content = fs.readFileSync(grammarPath); + const rawGrammar = tm.parseRawGrammar(content.toString(), grammarPath); + return registry.addGrammar(rawGrammar); } } @@ -552,7 +564,7 @@ export class FoldingFeature implements IFeature { * @param logger The logging object to send messages to * @returns The required module, or null if the module cannot be required */ - private getCoreNodeModule(moduleName: string, logger: Logger) { + private getCoreNodeModule(moduleName: string, logger: ILogger) { // Attempt to load the module from known locations const loadLocations: string[] = [ `${vscode.env.appRoot}/node_modules.asar/${moduleName}`, diff --git a/src/logging.ts b/src/logging.ts index 580f029c55..62d26a56a4 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -16,7 +16,19 @@ export enum LogLevel { Error, } -export class Logger { +/** Interface for logging operations. New features should use this interface for the "type" of logger. + * This will allow for easy mocking of the logger during unit tests. + */ +export interface ILogger { + write(message: string, ...additionalMessages: string[]); + writeDiagnostic(message: string, ...additionalMessages: string[]); + writeVerbose(message: string, ...additionalMessages: string[]); + writeWarning(message: string, ...additionalMessages: string[]); + writeAndShowWarning(message: string, ...additionalMessages: string[]); + writeError(message: string, ...additionalMessages: string[]); +} + +export class Logger implements ILogger { public logBasePath: string; public logSessionPath: string; diff --git a/test/features/folding.test.ts b/test/features/folding.test.ts index fd54f654bd..bfa25ec11e 100644 --- a/test/features/folding.test.ts +++ b/test/features/folding.test.ts @@ -24,12 +24,12 @@ function assertFoldingRegions(result, expected): void { suite("Features", () => { - suite("Folding Provider", () => { + suite("Folding Provider", async () => { const logger: MockLogger = new MockLogger(); const mockSelector: DocumentSelector = [ { language: "powershell", scheme: "file" }, ]; - const psGrammar = (new folding.FoldingFeature(logger, mockSelector)).grammar(logger); + const psGrammar = await (new folding.FoldingFeature(logger, mockSelector)).loadPSGrammar(logger); const provider = (new folding.FoldingProvider(psGrammar)); test("Can detect the PowerShell Grammar", () => { diff --git a/test/test_utils.ts b/test/test_utils.ts index fce7d8d56b..779654acb4 100644 --- a/test/test_utils.ts +++ b/test/test_utils.ts @@ -2,19 +2,9 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ -import { Logger, LogLevel } from "../src/logging"; - -export class MockLogger extends Logger { - // Note - This is not a true mock as the constructor is inherited and causes errors due to trying load - // the "PowerShell Extension Logs" multiple times. Ideally logging should be via an interface and then - // we can mock correctly. - - public dispose() { return undefined; } - - public getLogFilePath(baseName: string): string { return "mock"; } - - public writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]) { return undefined; } +import { ILogger } from "../src/logging"; +export class MockLogger implements ILogger { public write(message: string, ...additionalMessages: string[]) { return undefined; } public writeDiagnostic(message: string, ...additionalMessages: string[]) { return undefined; } @@ -28,6 +18,4 @@ export class MockLogger extends Logger { public writeError(message: string, ...additionalMessages: string[]) { return undefined; } public writeAndShowError(message: string, ...additionalMessages: string[]) { return undefined; } - - public startNewLog(minimumLogLevel: string = "Normal") { return undefined; } }