From f679ad4181add3044faff03087f680767feeebb2 Mon Sep 17 00:00:00 2001 From: Jesus David Garcia Gomez Date: Wed, 21 Mar 2018 15:21:55 -0700 Subject: [PATCH] Fix: Infinite loop when running the `--init` command Fix #908 Close #910 --- packages/sonarwhal/src/lib/cli/analyze.ts | 46 +++---- packages/sonarwhal/src/lib/cli/init.ts | 41 +++--- packages/sonarwhal/src/lib/config.ts | 144 ++++++++++---------- packages/sonarwhal/src/lib/utils/npm.ts | 5 +- packages/sonarwhal/tests/lib/cli/analyze.ts | 64 +++++---- packages/sonarwhal/tests/lib/cli/init.ts | 38 +++++- 6 files changed, 200 insertions(+), 138 deletions(-) diff --git a/packages/sonarwhal/src/lib/cli/analyze.ts b/packages/sonarwhal/src/lib/cli/analyze.ts index dcbb0902b7a..b3940e56e31 100644 --- a/packages/sonarwhal/src/lib/cli/analyze.ts +++ b/packages/sonarwhal/src/lib/cli/analyze.ts @@ -1,5 +1,6 @@ import { promisify } from 'util'; import { URL } from 'url'; +import * as path from 'path'; import * as async from 'async'; import * as inquirer from 'inquirer'; @@ -8,7 +9,7 @@ import * as pluralize from 'pluralize'; import { SonarwhalConfig } from '../config'; import { Sonarwhal } from '../sonarwhal'; -import { CLIOptions, ORA, Problem, Severity } from '../types'; +import { CLIOptions, ORA, Problem, Severity, UserConfig } from '../types'; import { debug as d } from '../utils/debug'; import { getAsUris } from '../utils/get-as-uri'; import * as logger from '../utils/logging'; @@ -64,34 +65,22 @@ const askUserToInstallDependencies = async (dependencies: Array): Promis return answer.confirm; }; -const tryToLoadConfig = async (actions: CLIOptions): Promise => { - let config: SonarwhalConfig; +const getUserConfig = (actions: CLIOptions): UserConfig => { + let config: UserConfig; const configPath: string = actions.config || SonarwhalConfig.getFilenameForDirectory(process.cwd()); if (!configPath) { - logger.error(`Couldn't find a valid path to load the configuration file.`); - - const created = await askUserToCreateConfig(); - - if (created) { - config = await tryToLoadConfig(actions); - } - - return config; + return null; } debug(`Loading configuration file from ${configPath}.`); try { - config = SonarwhalConfig.fromFilePath(configPath, actions); + const resolvedPath: string = path.resolve(process.cwd(), configPath); + + config = SonarwhalConfig.loadConfigFile(resolvedPath); } catch (e) { logger.error(e); - - logger.log(`Couldn't load a valid configuration file in ${configPath}.`); - const created = await askUserToCreateConfig(); - - if (created) { - config = await tryToLoadConfig(actions); - } + config = null; } return config; @@ -150,14 +139,23 @@ export const analyze = async (actions: CLIOptions): Promise => { return false; } - const config: SonarwhalConfig = await tryToLoadConfig(actions); + let userConfig: UserConfig = await getUserConfig(actions); + + if (!userConfig) { + logger.error(`Couldn't find a valid path to load the configuration file.`); - if (!config) { - logger.error(`Unable to find a valid configuration file. Please add a .sonarwhalrc file by running 'sonarwhal --init'. `); + const created = await askUserToCreateConfig(); - return false; + if (created) { + userConfig = await getUserConfig(actions); + } else { + logger.error(`Unable to find a valid configuration file. Please add a .sonarwhalrc file by running 'sonarwhal --init'. `); + + return false; + } } + const config = SonarwhalConfig.fromConfig(userConfig, actions); const resources = resourceLoader.loadResources(config); if (resources.missing.length > 0 || resources.incompatible.length > 0) { diff --git a/packages/sonarwhal/src/lib/cli/init.ts b/packages/sonarwhal/src/lib/cli/init.ts index 9ff9fdf398b..f21fcf4978e 100644 --- a/packages/sonarwhal/src/lib/cli/init.ts +++ b/packages/sonarwhal/src/lib/cli/init.ts @@ -26,6 +26,11 @@ import { NpmPackage } from '../types'; const debug: debug.IDebugger = d(__filename); const defaultFormatter = 'summary'; +type InitUserConfig = { + config: UserConfig; + packages?: Array; +}; + /** Validates if the given array is not empty and if so, prints an error message. */ const anyResources = (resources: Array, type: string) => { if (resources.length > 0) { @@ -44,7 +49,7 @@ const getConfigurationName = (pkgName: string): string => { }; /** Shwos the user a list of official configuration packages available in npm to install. */ -const extendConfig = async (): Promise => { +const extendConfig = async (): Promise => { const configPackages: Array = await getOfficialPackages(ResourceType.configuration); if (!anyResources(configPackages, ResourceType.configuration)) { @@ -68,18 +73,15 @@ const extendConfig = async (): Promise => { const answers: inquirer.Answers = await inquirer.prompt(questions); const sonarwhalConfig = { extends: [getConfigurationName(answers.configuration)] }; - const installed = await installPackages([answers.configuration]); - // Maybe there was an error during the installation, the error and message output is handled in the npm package - if (!installed) { - return null; - } - - return sonarwhalConfig; + return { + config: sonarwhalConfig, + packages: [answers.configuration] + }; }; /** Prompts a series of questions to create a new configuration object based on the installed packages. */ -const customConfig = async (): Promise => { +const customConfig = async (): Promise => { const connectorKeys: Array = getInstalledResources(ResourceType.connector).concat(getCoreResources(ResourceType.connector)); const formattersKeys: Array = getInstalledResources(ResourceType.formatter).concat(getCoreResources(ResourceType.formatter)); const parsersKeys: Array = getInstalledResources(ResourceType.parser).concat(getCoreResources(ResourceType.parser)); @@ -153,7 +155,7 @@ const customConfig = async (): Promise => { sonarwhalConfig.browserslist = await generateBrowserslistConfig(); - return sonarwhalConfig; + return { config: sonarwhalConfig }; }; /** @@ -180,18 +182,23 @@ export const initSonarwhalrc = async (options: CLIOptions): Promise => const initialAnswer: inquirer.Answers = await inquirer.prompt(initialQuestion); - const sonarwhalConfig = initialAnswer.configType === 'predefined' ? + const result = initialAnswer.configType === 'predefined' ? await extendConfig() : await customConfig(); - // No resources installed or no configuration packages available, we have to quit - if (!sonarwhalConfig) { - return true; - } - const filePath: string = path.join(process.cwd(), '.sonarwhalrc'); - await promisify(fs.writeFile)(filePath, JSON.stringify(sonarwhalConfig, null, 4), 'utf8'); + await promisify(fs.writeFile)(filePath, JSON.stringify(result.config, null, 4), 'utf8'); + + if (Array.isArray(result.packages) && result.packages.length > 0) { + const isInstalled = getInstalledResources(ResourceType.configuration).includes(getConfigurationName(result.packages[0])); + + if (isInstalled) { + return true; + } + + await installPackages(result.packages); + } return true; }; diff --git a/packages/sonarwhal/src/lib/config.ts b/packages/sonarwhal/src/lib/config.ts index 3b9ee4a7213..15a3ffb4db4 100644 --- a/packages/sonarwhal/src/lib/config.ts +++ b/packages/sonarwhal/src/lib/config.ts @@ -57,76 +57,6 @@ const loadPackageJSONConfigFile = (filePath: string): UserConfig => { } }; -/** - * Loads a configuration file regardless of the source. Inspects the file path - * to determine the correctly way to load the config file. - */ -const loadConfigFile = (filePath: string): UserConfig => { - - let config: UserConfig; - - switch (path.extname(filePath)) { - case '': - case '.json': - if (path.basename(filePath) === 'package.json') { - config = loadPackageJSONConfigFile(filePath); - } else { - config = loadJSONFile(filePath); - } - break; - - case '.js': - config = loadJSFile(filePath); - break; - - default: - config = null; - } - - return config; -}; - -/** - * Generates the list of browsers to target using the `browserslist` property - * of the `sonarwhal` configuration or `package.json` or uses the default one - */ -const loadBrowsersList = (config: UserConfig) => { - const directory: string = process.cwd(); - const files: Array = CONFIG_FILES.reduce((total, configFile) => { - const filename: string = path.join(directory, configFile); - - if (shell.test('-f', filename)) { - total.push(filename); - } - - return total; - }, []); - - if (!config.browserslist) { - for (let i = 0; i < files.length; i++) { - const file: string = files[i]; - const tmpConfig: UserConfig = loadConfigFile(file); - - if (tmpConfig && tmpConfig.browserslist) { - config.browserslist = tmpConfig.browserslist; - break; - } - - if (file.endsWith('package.json')) { - const packagejson = loadJSONFile(file); - - config.browserslist = packagejson.browserslist; - } - } - } - - if (!config.browserslist || config.browserslist.length === 0) { - return browserslist(); - } - - return browserslist(config.browserslist); -}; - // ------------------------------------------------------------------------ /** @@ -218,6 +148,76 @@ export class SonarwhalConfig { } } + /** + * Generates the list of browsers to target using the `browserslist` property + * of the `sonarwhal` configuration or `package.json` or uses the default one + */ + public static loadBrowsersList(config: UserConfig) { + const directory: string = process.cwd(); + const files: Array = CONFIG_FILES.reduce((total, configFile) => { + const filename: string = path.join(directory, configFile); + + if (shell.test('-f', filename)) { + total.push(filename); + } + + return total; + }, []); + + if (!config.browserslist) { + for (let i = 0; i < files.length; i++) { + const file: string = files[i]; + const tmpConfig: UserConfig = SonarwhalConfig.loadConfigFile(file); + + if (tmpConfig && tmpConfig.browserslist) { + config.browserslist = tmpConfig.browserslist; + break; + } + + if (file.endsWith('package.json')) { + const packagejson = loadJSONFile(file); + + config.browserslist = packagejson.browserslist; + } + } + } + + if (!config.browserslist || config.browserslist.length === 0) { + return browserslist(); + } + + return browserslist(config.browserslist); + } + + + /** + * Loads a configuration file regardless of the source. Inspects the file path + * to determine the correctly way to load the config file. + */ + public static loadConfigFile(filePath: string): UserConfig { + let config: UserConfig; + + switch (path.extname(filePath)) { + case '': + case '.json': + if (path.basename(filePath) === 'package.json') { + config = loadPackageJSONConfigFile(filePath); + } else { + config = loadJSONFile(filePath); + } + break; + + case '.js': + config = loadJSFile(filePath); + break; + + default: + config = null; + } + + return config; + } + public static fromConfig(config: UserConfig, actions?: CLIOptions): SonarwhalConfig { if (!config) { @@ -291,10 +291,10 @@ export class SonarwhalConfig { // 1 const resolvedPath: string = path.resolve(process.cwd(), filePath); - const userConfig = loadConfigFile(resolvedPath); + const userConfig = SonarwhalConfig.loadConfigFile(resolvedPath); const config = this.fromConfig(userConfig, actions); - userConfig.browserslist = userConfig.browserslist || loadBrowsersList(userConfig); + userConfig.browserslist = userConfig.browserslist || SonarwhalConfig.loadBrowsersList(userConfig); return config; } diff --git a/packages/sonarwhal/src/lib/utils/npm.ts b/packages/sonarwhal/src/lib/utils/npm.ts index 4d1c4b0f757..25030e156a0 100644 --- a/packages/sonarwhal/src/lib/utils/npm.ts +++ b/packages/sonarwhal/src/lib/utils/npm.ts @@ -20,6 +20,9 @@ export const installPackages = (packages: Array): boolean => { let packagePath: string; /** Current working directory. */ const currentWorkingDir = process.cwd(); + /** Wheter or not the process is running in windows */ + const isWindows = process.platform === 'win32'; + /** Command to install the packages. */ let command: string = `npm install ${packages.join(' ')}`; @@ -73,7 +76,7 @@ export const installPackages = (packages: Array): boolean => { * Show message to install packages manually (maybe permissions error?). */ logger.error(`Try executing: - ${process.platform !== 'win32' ? 'sudo ' : ''}${command} + ${!isWindows && global ? 'sudo ' : ''}${command} manually to install all the packages.`); } diff --git a/packages/sonarwhal/tests/lib/cli/analyze.ts b/packages/sonarwhal/tests/lib/cli/analyze.ts index c810210c9fb..4ac76338a4d 100644 --- a/packages/sonarwhal/tests/lib/cli/analyze.ts +++ b/packages/sonarwhal/tests/lib/cli/analyze.ts @@ -29,8 +29,9 @@ const logger = { const config = { SonarwhalConfig: { - fromFilePath() { }, + fromConfig() { }, getFilenameForDirectory() { }, + loadConfigFile() { }, validateRulesConfig() { } } }; @@ -96,7 +97,8 @@ test.serial('If config is not defined, it should get the config file from the di missing: [] }); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath') + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile') .onFirstCall() .returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); @@ -119,7 +121,9 @@ test.serial('If config path doesn\'t exist, it should create a configuration fil .returns(null) .onSecondCall() .returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); sandbox.stub(inquirer, 'prompt').resolves({ confirm: true }); @@ -144,11 +148,12 @@ test.serial('If config file does not exist, it should create a configuration fil missing: [] }); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath') + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile') .onFirstCall() .throws(error) .onSecondCall() .returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); sandbox.stub(inquirer, 'prompt').resolves({ confirm: true }); @@ -171,10 +176,9 @@ test.serial('If config file does not exist and user refuses to create a configur missing: [] }); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath') + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile') .onFirstCall() .throws(error); - sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); sandbox.stub(inquirer, 'prompt').resolves({ confirm: false }); const result = await analyzer.analyze(actions); @@ -195,7 +199,8 @@ test.serial('If configuration file exists, it should use it', async (t) => { missing: [] }); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); const customConfigOptions = ({ _: ['http://localhost'], config: 'configfile.cfg' } as CLIOptions); @@ -203,7 +208,7 @@ test.serial('If configuration file exists, it should use it', async (t) => { await analyzer.analyze(customConfigOptions); t.false(t.context.SonarwhalConfig.getFilenameForDirectory.called); - t.is(t.context.SonarwhalConfig.fromFilePath.args[0][0], 'configfile.cfg'); + t.true(t.context.SonarwhalConfig.loadConfigFile.args[0][0].endsWith('configfile.cfg')); sandbox.restore(); }); @@ -238,7 +243,8 @@ test.serial('If executeOn returns an error, it should exit with code 1 and call sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); sandbox.stub(inquirer, 'prompt').resolves({ confirm: false }); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); const exitCode = await analyzer.analyze(actions); @@ -256,7 +262,8 @@ test.serial('If executeOn returns an error, it should call to spinner.fail()', a missing: [] }); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); sandbox.stub(sonarwhalContainer.Sonarwhal.prototype, 'executeOn').resolves([{ severity: Severity.error }]); @@ -275,7 +282,8 @@ test.serial('If executeOn throws an exception, it should exit with code 1', asyn missing: [] }); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(sonarwhalContainer.Sonarwhal.prototype, 'executeOn').throws(new Error()); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); @@ -294,7 +302,8 @@ test.serial('If executeOn throws an exception, it should call to spinner.fail()' missing: [] }); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(sonarwhalContainer.Sonarwhal.prototype, 'executeOn').throws(new Error()); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); @@ -333,7 +342,8 @@ test.serial('If executeOn returns no errors, it should exit with code 0 and call sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); const exitCode = await analyzer.analyze(actions); @@ -372,7 +382,8 @@ test.serial('If executeOn returns no errors, it should call to spinner.succeed() sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); await analyzer.analyze(actions); @@ -411,7 +422,8 @@ test.serial('Event fetch::start should write a message in the spinner', async (t }); sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); await analyzer.analyze(actions); @@ -450,7 +462,8 @@ test.serial('Event fetch::end should write a message in the spinner', async (t) }); sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); await analyzer.analyze(actions); @@ -489,7 +502,8 @@ test.serial('Event fetch::end::manifest should write a message in the spinner', }); sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); await analyzer.analyze(actions); @@ -528,7 +542,8 @@ test.serial('Event fetch::end::html should write a message in the spinner', asyn }); sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); await analyzer.analyze(actions); @@ -566,8 +581,9 @@ test.serial('Event traverse::up should write a message in the spinner', async (t await analyzer.sonarwhal.emitAsync('traverse::up', { resource: 'http://localhost/' }); }); sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); - sandbox.stub(config.SonarwhalConfig, 'getFilenameForDirectory').resolves('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(config.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); await analyzer.analyze(actions); @@ -606,7 +622,8 @@ test.serial('Event traverse::end should write a message in the spinner', async ( }); sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); sandbox.stub(t.context.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); await analyzer.analyze(actions); @@ -644,8 +661,9 @@ test.serial('Event scan::end should write a message in the spinner', async (t) = await analyzer.sonarwhal.emitAsync('scan::end', { resource: 'http://localhost/' }); }); sandbox.stub(sonarwhalContainer, 'Sonarwhal').returns(sonarwhalObj); - sandbox.stub(config.SonarwhalConfig, 'getFilenameForDirectory').resolves('/config/path'); - sandbox.stub(t.context.SonarwhalConfig, 'fromFilePath').returns({}); + sandbox.stub(config.SonarwhalConfig, 'getFilenameForDirectory').returns('/config/path'); + sandbox.stub(t.context.SonarwhalConfig, 'loadConfigFile').returns({}); + sandbox.stub(t.context.SonarwhalConfig, 'fromConfig').returns({}); sandbox.stub(t.context.SonarwhalConfig, 'validateRulesConfig').returns(validateRulesConfigResult); await analyzer.analyze(actions); diff --git a/packages/sonarwhal/tests/lib/cli/init.ts b/packages/sonarwhal/tests/lib/cli/init.ts index 9527752b4a2..3fa2759efae 100644 --- a/packages/sonarwhal/tests/lib/cli/init.ts +++ b/packages/sonarwhal/tests/lib/cli/init.ts @@ -81,7 +81,7 @@ const installedConnectors = [ const installedParsers = []; -test.serial('initSonarwhalrc should install the configuration package if user chooses a recommended configuration', async (t) => { +test.serial(`initSonarwhalrc should install the configuration package if user chooses a recommended configuration and the configuration doesn't exists`, async (t) => { const sandbox = sinon.sandbox.create(); const initAnswers = { configType: 'predefined' }; const configAnswer = { configuration: '@sonarwhal/configuration-recommended' }; @@ -97,6 +97,8 @@ test.serial('initSonarwhalrc should install the configuration package if user ch const stub = sandbox.stub(npm, 'installPackages').returns(true); + sandbox.stub(resourceLoader, 'getInstalledResources').returns([]); + sandbox.stub(inquirer, 'prompt') .onFirstCall() .resolves(initAnswers) @@ -113,6 +115,39 @@ test.serial('initSonarwhalrc should install the configuration package if user ch sandbox.restore(); }); +test.serial(`initSonarwhalrc shouldn't install the configuration package if user chooses a recommended configuration and the configuration already exists`, async (t) => { + const sandbox = sinon.sandbox.create(); + const initAnswers = { configType: 'predefined' }; + const configAnswer = { configuration: '@sonarwhal/configuration-recommended' }; + + sandbox.stub(npm, 'getOfficialPackages').resolves([{ + date: null, + description: '', + keywords: [], + maintainers: [], + name: '@sonarwhal/configuration-recommended', + version: '1.0.0' + }] as Array); + + const stub = sandbox.stub(npm, 'installPackages').returns(true); + + sandbox.stub(resourceLoader, 'getInstalledResources').returns(['recommended']); + + sandbox.stub(inquirer, 'prompt') + .onFirstCall() + .resolves(initAnswers) + .onSecondCall() + .resolves(configAnswer); + + await initSonarwhalrc(actions); + + const fileData = JSON.parse(t.context.promisify.args[0][1]); + + t.false(stub.called, `npm has tried to install any package`); + t.true(_.isEqual(fileData, { extends: ['recommended'] })); + + sandbox.restore(); +}); test.serial(`"inquirer.prompt" should use the installed resources if the user doesn't want a predefined configuration`, async (t) => { const sandbox = sinon.sandbox.create(); @@ -178,6 +213,7 @@ test.serial(`if instalation of a config package fails, "initSonarwhalrc" returns }] as Array); sandbox.stub(npm, 'installPackages').returns(false); + sandbox.stub(resourceLoader, 'getInstalledResources').returns([]); sandbox.stub(inquirer, 'prompt') .onFirstCall()