Repository: okonet/lint-staged
Pull request #1055 (2021-11-21 17:09:40)

Configuration fixes
See issue nodejs/node#34765

This fixes https://github.com/okonet/lint-staged/issues/1054

codecov[bot]: # [Codecov](https://codecov.io/gh/okonet/lint-staged/pull/1055?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov) Report
> Merging [#1055](https://codecov.io/gh/okonet/lint-staged/pull/1055?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov) (04e114c) into [master](https://codecov.io/gh/okonet/lint-staged/commit/e96b6d9674f07b4686876cb40605274577925973?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov) (e96b6d9) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/okonet/lint-staged/pull/1055/graphs/tree.svg?width=650&height=150&src=pr&token=LSoEgsjk4O&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov)](https://codecov.io/gh/okonet/lint-staged/pull/1055?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov)

```diff
@@            Coverage Diff            @@
##            master     #1055   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           21        21           
  Lines          591       591           
  Branches       157       157           
=========================================
  Hits           591       591           
```


| [Impacted Files](https://codecov.io/gh/okonet/lint-staged/pull/1055?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov) | Coverage Δ | |
|---|---|---|
| [lib/loadConfig.js](https://codecov.io/gh/okonet/lint-staged/pull/1055/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov#diff-bGliL2xvYWRDb25maWcuanM=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/okonet/lint-staged/pull/1055?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/okonet/lint-staged/pull/1055?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov). Last update [e96b6d9...04e114c](https://codecov.io/gh/okonet/lint-staged/pull/1055?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andrey+Okonetchnikov).

iiroj: Ping @okonet yet another issue not caught by tests because of Babel transpilation. 😅 
iiroj: I also pushed https://github.com/okonet/lint-staged/pull/1055/commits/b00ca79eacba15bcf6e9b58849f6e930490184b2 which fixes https://github.com/okonet/lint-staged/issues/1056 and https://github.com/okonet/lint-staged/issues/1057
iiroj: Ping @okonet let's get these unfortunate bugs fixed!

Patch:
diff --git a/lib/index.js b/lib/index.js
index 8384898..9f649d4 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -58,20 +58,14 @@ const lintStaged = async (
 ) => {
   await validateOptions({ shell }, logger)
 
-  debugLog('Loading config using `lilconfig`')
+  const inputConfig = configObject || (await loadConfig(configPath, logger))
 
-  const resolved = configObject
-    ? { config: configObject, filepath: '(input)' }
-    : await loadConfig(configPath)
-
-  if (!resolved) {
+  if (!inputConfig) {
     logger.error(`${ConfigNotFoundError.message}.`)
     throw ConfigNotFoundError
   }
 
-  debugLog('Successfully loaded config from `%s`:\n%O', resolved.filepath, resolved.config)
-
-  const config = validateConfig(resolved.config, logger)
+  const config = validateConfig(inputConfig, logger)
 
   if (debug) {
     // Log using logger to be able to test through `consolemock`.
diff --git a/lib/loadConfig.js b/lib/loadConfig.js
index 2ee43e0..3e4b1b6 100644
--- a/lib/loadConfig.js
+++ b/lib/loadConfig.js
@@ -1,6 +1,11 @@
+import { pathToFileURL } from 'url'
+
+import debug from 'debug'
 import { lilconfig } from 'lilconfig'
 import YAML from 'yaml'
 
+const debugLog = debug('lint-staged:loadConfig')
+
 /**
  * The list of files `lint-staged` will read configuration
  * from, in the declared order.
@@ -20,16 +25,12 @@ const searchPlaces = [
 ]
 
 /** exported for tests */
-export const dynamicImport = (path) =>
-  import(path)
-    .then((module) => module.default)
-    .catch((error) => {
-      console.error(error)
-      throw error
-    })
+export const dynamicImport = (path) => import(pathToFileURL(path)).then((module) => module.default)
 
 const jsonParse = (path, content) => JSON.parse(content)
 
+const yamlParse = (path, content) => YAML.parse(content)
+
 /**
  * `lilconfig` doesn't support yaml files by default,
  * so we add custom loaders for those. Files without
@@ -41,9 +42,9 @@ const loaders = {
   '.json': jsonParse,
   '.mjs': dynamicImport,
   '.cjs': dynamicImport,
-  '.yaml': YAML.parse,
-  '.yml': YAML.parse,
-  noExt: YAML.parse,
+  '.yaml': yamlParse,
+  '.yml': yamlParse,
+  noExt: yamlParse,
 }
 
 const resolveConfig = (configPath) => {
@@ -56,14 +57,30 @@ const resolveConfig = (configPath) => {
 
 /**
  * @param {string} [configPath]
+ * @param {Logger} [logger]
  */
-export const loadConfig = async (configPath) => {
-  const explorer = lilconfig('lint-staged', { searchPlaces, loaders })
-  const result = await (configPath ? explorer.load(resolveConfig(configPath)) : explorer.search())
-  if (!result) return null
+export const loadConfig = async (configPath, logger) => {
+  try {
+    if (configPath) {
+      debugLog('Loading configuration from `%s`...', configPath)
+    } else {
+      debugLog('Searching for configuration...')
+    }
 
-  const { config, filepath } = result
+    const explorer = lilconfig('lint-staged', { searchPlaces, loaders })
 
-  // config is a promise when using the `dynamicImport` loader
-  return { config: await config, filepath }
+    const result = await (configPath ? explorer.load(resolveConfig(configPath)) : explorer.search())
+    if (!result) return null
+
+    // config is a promise when using the `dynamicImport` loader
+    const config = await result.config
+
+    debugLog('Successfully loaded config from `%s`:\n%O', result.filepath, config)
+
+    return config
+  } catch (error) {
+    debugLog('Failed to load configuration from `%s`', configPath)
+    logger.error(error)
+    return null
+  }
 }


Test patch:
diff --git a/test/__mocks__/my-config.yml b/test/__mocks__/my-config.yml
new file mode 100644
index 0000000..85079e9
--- /dev/null
+++ b/test/__mocks__/my-config.yml
@@ -0,0 +1,1 @@
+'*': 'mytask'
diff --git a/test/index.spec.js b/test/index.spec.js
index 4f2b779..004ef74 100644
--- a/test/index.spec.js
+++ b/test/index.spec.js
@@ -18,6 +18,14 @@ const mockLilConfig = (result) => {
   }))
 }
 
+/**
+ * This converts paths into `file://` urls, but this doesn't
+ * work with `import()` when using babel + jest.
+ */
+jest.mock('url', () => ({
+  pathToFileURL: (path) => path,
+}))
+
 jest.mock('../lib/getStagedFiles')
 jest.mock('../lib/gitWorkflow')
 jest.mock('../lib/validateOptions', () => ({
@@ -149,6 +157,27 @@ describe('lintStaged', () => {
     `)
   })
 
+  it('should load YAML config file', async () => {
+    expect.assertions(1)
+
+    await lintStaged(
+      {
+        configPath: path.join(__dirname, '__mocks__', 'my-config.yml'),
+        debug: true,
+        quiet: true,
+      },
+      logger
+    )
+
+    expect(logger.printHistory()).toMatchInlineSnapshot(`
+      "
+      LOG Running lint-staged with the following config:
+      LOG {
+        '*': 'mytask'
+      }"
+    `)
+  })
+
   it('should load CommonJS config file from absolute path', async () => {
     expect.assertions(1)
 
@@ -298,7 +327,7 @@ describe('lintStaged', () => {
   })
 
   it('should print helpful error message when explicit config file is not found', async () => {
-    expect.assertions(2)
+    expect.assertions(3)
 
     const nonExistentConfig = 'fake-config-file.yml'
 
@@ -314,6 +343,7 @@ describe('lintStaged', () => {
       lintStaged({ configPath: nonExistentConfig, quiet: true }, logger)
     ).rejects.toThrowError()
 
-    expect(logger.printHistory()).toMatchInlineSnapshot(`""`)
+    expect(logger.printHistory()).toMatch('ENOENT')
+    expect(logger.printHistory()).toMatch('Configuration could not be found')
   })
 })
diff --git a/test/loadConfig.spec.js b/test/loadConfig.spec.js
index 533177a..33272e6 100644
--- a/test/loadConfig.spec.js
+++ b/test/loadConfig.spec.js
@@ -1,21 +1,7 @@
-import makeConsoleMock from 'consolemock'
-
 import { dynamicImport } from '../lib/loadConfig.js'
 
 describe('dynamicImport', () => {
-  const globalConsoleTemp = console
-
-  beforeEach(() => {
-    console = makeConsoleMock()
-  })
-
-  afterAll(() => {
-    console = globalConsoleTemp
-  })
-
   it('should log errors into console', () => {
-    expect(() => dynamicImport('not-found.js')).rejects.toMatchInlineSnapshot(
-      `[Error: Cannot find module 'not-found.js' from 'lib/loadConfig.js']`
-    )
+    expect(() => dynamicImport('not-found.js')).rejects.toThrowError(`Cannot find module`)
   })
 })