diff --git a/package-lock.json b/package-lock.json index d244c35..9307fc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "AGPL-3.0", "dependencies": { "convert-array-to-csv": "^2.0.0", + "cosmiconfig": "^9.0.0", "lightning-flow-scanner-core": "4.45.0", "tabulator-tables": "^6.3.1", "uuid": "^11.0.5", @@ -366,7 +367,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -649,7 +649,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -9625,8 +9624,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-query": { "version": "5.3.0", @@ -10472,7 +10470,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11432,6 +11429,50 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -12757,6 +12798,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", @@ -12786,7 +12836,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -14816,7 +14865,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -14997,7 +15045,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, "license": "MIT" }, "node_modules/is-binary-path": { @@ -16332,14 +16379,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -16375,8 +16420,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-ref-resolver": { "version": "1.0.1", @@ -16911,8 +16955,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/linkify-it": { "version": "5.0.0", @@ -18289,7 +18332,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -18637,7 +18679,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -20013,7 +20054,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -23015,7 +23055,7 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 9a9a215..d6ddb7b 100644 --- a/package.json +++ b/package.json @@ -182,6 +182,7 @@ }, "dependencies": { "convert-array-to-csv": "^2.0.0", + "cosmiconfig": "^9.0.0", "lightning-flow-scanner-core": "4.45.0", "tabulator-tables": "^6.3.1", "uuid": "^11.0.5", diff --git a/src/commands/handlers.ts b/src/commands/handlers.ts index fb5353b..946b0e7 100644 --- a/src/commands/handlers.ts +++ b/src/commands/handlers.ts @@ -9,6 +9,8 @@ import { CacheProvider } from '../providers/cache-provider'; import { testdata } from '../store/testdata'; import { OutputChannel } from '../providers/outputChannel'; +const { USE_NEW_CONFIG: isUseNewConfig } = process.env; + export default class Commands { constructor(private context: vscode.ExtensionContext) {} @@ -30,6 +32,10 @@ export default class Commands { } private async configRules() { + if (isUseNewConfig) { + await this.ruleConfiguration(); + return; + } const allRules: core.IRuleDefinition[] = [ ...core.getBetaRules(), ...core.getRules(), @@ -94,6 +100,8 @@ export default class Commands { ); } + private async ruleConfiguration() {} + private async debugView() { let results = testdata as unknown as core.ScanResult[]; await CacheProvider.instance.set('results', results); diff --git a/src/providers/config-provider.ts b/src/providers/config-provider.ts new file mode 100644 index 0000000..d6b2d83 --- /dev/null +++ b/src/providers/config-provider.ts @@ -0,0 +1,28 @@ +import { cosmiconfig, CosmiconfigResult } from 'cosmiconfig'; +import { IRulesConfig } from 'lightning-flow-scanner-core'; + +export class ConfigProvider { + public async loadConfig(configPath?: string): Promise { + const moduleName = 'flow-scanner'; + const searchPlaces = [ + 'package.json', + `.${moduleName}.yaml`, + `.${moduleName}.yml`, + `.${moduleName}.json`, + `config/.${moduleName}.yaml`, + `config/.${moduleName}.yml`, + `.flow-scanner`, + ]; + const explorer = cosmiconfig(moduleName, { + searchPlaces, + }); + let explorerResults: CosmiconfigResult; + if (configPath) { + // Forced config file name + explorerResults = await explorer.load(configPath); + } + // Let cosmiconfig look for a config file + explorerResults = explorerResults ?? (await explorer.search()); + return explorerResults?.config ?? {}; + } +} diff --git a/tests/commands/handlers.spec.ts b/tests/commands/handlers.spec.ts index fbb107e..0fe44b9 100644 --- a/tests/commands/handlers.spec.ts +++ b/tests/commands/handlers.spec.ts @@ -52,7 +52,7 @@ describe('Commands', () => { CacheProvider.instance = instanceMock as any; const outputMock = { logChannel: { debug: jest.fn() } }; - const outputSpy = jest.spyOn(OutputChannel, 'getInstance'); //.getInstance = outputMock as any; + const outputSpy = jest.spyOn(OutputChannel, 'getInstance'); outputSpy.mockReturnValue(outputMock as any); const extensionContext = jest.fn(); @@ -60,11 +60,17 @@ describe('Commands', () => { extensionContext as unknown as ExtensionContext ); - await command['configRules'](); + await expect(async () => await command['configRules']()).not.toThrow(); }); - it('should read from configuration', async () => {}); + it('should read from configuration', async () => { + const command = new cmd.default({} as any); + await expect(command['ruleConfiguration']()).resolves.not.toThrow(); + }); - it('should write to configuration', async () => {}); + it('should write to configuration', async () => { + const command = new cmd.default({} as any); + await expect(command['ruleConfiguration']()).resolves.not.toThrow(); + }); }); }); diff --git a/tests/providers/config-provider.spec.ts b/tests/providers/config-provider.spec.ts new file mode 100644 index 0000000..e7361a0 --- /dev/null +++ b/tests/providers/config-provider.spec.ts @@ -0,0 +1,86 @@ +import { describe, it, expect, jest } from '@jest/globals'; + +import { ConfigProvider } from '../../src/providers/config-provider'; +import * as cosmi from 'cosmiconfig'; + +jest.mock('cosmiconfig'); + +describe('Config-Provider', () => { + it('should be defined', () => { + expect(ConfigProvider).toBeDefined(); + }); + + it('should not error when no config', async () => { + const cosmiMock = { + load: jest.fn(), + search: jest.fn(), + }; + const cosmiSpy = jest.spyOn(cosmi, 'cosmiconfig'); + cosmiSpy.mockReturnValue(cosmiMock as any); + + const configProvider = new ConfigProvider(); + await expect(configProvider.loadConfig()).resolves.toStrictEqual({}); + }); + + it('should load config when directly passed from settings', async () => { + const cosmiMock = { + load: jest.fn().mockImplementation(() => ({ + config: { + rules: { + FlowName: { + severity: 'error', + }, + }, + exceptions: {}, + }, + filepath: '', + })), + search: jest.fn(), + }; + const cosmiSpy = jest.spyOn(cosmi, 'cosmiconfig'); + cosmiSpy.mockReturnValue(cosmiMock as any); + + const configProvider = new ConfigProvider(); + await expect( + configProvider.loadConfig('some config') + ).resolves.toStrictEqual({ + rules: { + FlowName: { + severity: 'error', + }, + }, + exceptions: {}, + }); + expect(cosmiMock.search).not.toHaveBeenCalled(); + }); + + it('should resolve a config via workspace directory', async () => { + const cosmiMock = { + load: jest.fn(), + search: jest.fn().mockImplementation(() => ({ + config: { + rules: { + APIVersion: { + severity: 'error', + }, + }, + exceptions: {}, + }, + filepath: '', + })), + }; + const cosmiSpy = jest.spyOn(cosmi, 'cosmiconfig'); + cosmiSpy.mockReturnValue(cosmiMock as any); + + const configProvider = new ConfigProvider(); + await expect(configProvider.loadConfig()).resolves.toStrictEqual({ + rules: { + APIVersion: { + severity: 'error', + }, + }, + exceptions: {}, + }); + expect(cosmiMock.load).not.toHaveBeenCalled(); + }); +});