Repository: pre-commit/pre-commit

Issue:
warn on unknown keys at the top level
We're now warning on unknown keys at the hook level, this should also happen at the top level


Discussion:
I wish to work on this issue.
I'm pre-commit newbie. 
can you refer me to the warning on unknown keys at the hook level?
sure!  @mitzkia started some work on this in #980 and added a [`WarnAdditionalKeys`](https://github.com/asottile/cfgv/pull/17) helper to cfgv to make this easier to do -- but looks like they've been busy with other things :)

the extraneous keys for the hook level is done later in the process [here](https://github.com/pre-commit/pre-commit/blob/8df6b22f4fc93b30be322e62f8bf1e0886729648/pre_commit/repository.py#L105-L110) -- though for the top level and repository level this should probably get done in pre_commit/clientlib.py (on the two mapping types directly) via the new `WarnAdditionalKeys`

Resolving patch:
diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py
--- a/pre_commit/clientlib.py
+++ b/pre_commit/clientlib.py
@@ -3,6 +3,7 @@
 
 import argparse
 import functools
+import logging
 import pipes
 import sys
 
@@ -15,6 +16,8 @@
 from pre_commit.languages.all import all_languages
 from pre_commit.util import parse_version
 
+logger = logging.getLogger('pre_commit')
+
 
 def check_type_tag(tag):
     if tag not in ALL_TAGS:
@@ -106,6 +109,8 @@ def validate_manifest_main(argv=None):
 
 
 class MigrateShaToRev(object):
+    key = 'rev'
+
     @staticmethod
     def _cond(key):
         return cfgv.Conditional(
@@ -144,6 +149,14 @@ def _entry(modname):
     )
 
 
+def warn_unknown_keys(extra, orig_keys):
+    logger.warning(
+        'Unexpected config key(s): {}'.format(
+            ', '.join(sorted(extra)),
+        ),
+    )
+
+
 _meta = (
     (
         'check-hooks-apply', (
@@ -222,6 +235,10 @@ def _entry(modname):
     ),
 
     MigrateShaToRev(),
+    cfgv.WarnAdditionalKeys(
+        ('repo', 'rev', 'hooks'),
+        warn_unknown_keys,
+    ),
 )
 DEFAULT_LANGUAGE_VERSION = cfgv.Map(
     'DefaultLanguageVersion', None,
@@ -247,6 +264,17 @@ def _entry(modname):
         cfgv.check_and(cfgv.check_string, check_min_version),
         '0',
     ),
+    cfgv.WarnAdditionalKeys(
+        (
+            'repos',
+            'default_language_version',
+            'default_stages',
+            'exclude',
+            'fail_fast',
+            'minimum_pre_commit_version',
+        ),
+        warn_unknown_keys,
+    ),
 )
 
 


Test patch:
diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py
--- a/tests/clientlib_test.py
+++ b/tests/clientlib_test.py
@@ -1,5 +1,7 @@
 from __future__ import unicode_literals
 
+import logging
+
 import cfgv
 import pytest
 
@@ -116,6 +118,48 @@ def test_validate_config_old_list_format_ok(tmpdir):
     assert not validate_config_main((f.strpath,))
 
 
+def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog):
+    f = tmpdir.join('cfg.yaml')
+    f.write(
+        '-   repo: https://gitlab.com/pycqa/flake8\n'
+        '    rev: 3.7.7\n'
+        '    hooks:\n'
+        '    -   id: flake8\n'
+        '    args: [--some-args]\n',
+    )
+    ret_val = validate_config_main((f.strpath,))
+    assert not ret_val
+    assert caplog.record_tuples == [
+        (
+            'pre_commit',
+            logging.WARNING,
+            'Unexpected config key(s): args',
+        ),
+    ]
+
+
+def test_validate_warn_on_unknown_keys_at_top_level(tmpdir, caplog):
+    f = tmpdir.join('cfg.yaml')
+    f.write(
+        'repos:\n'
+        '-   repo: https://gitlab.com/pycqa/flake8\n'
+        '    rev: 3.7.7\n'
+        '    hooks:\n'
+        '    -   id: flake8\n'
+        'foo:\n'
+        '    id: 1.0.0\n',
+    )
+    ret_val = validate_config_main((f.strpath,))
+    assert not ret_val
+    assert caplog.record_tuples == [
+        (
+            'pre_commit',
+            logging.WARNING,
+            'Unexpected config key(s): foo',
+        ),
+    ]
+
+
 @pytest.mark.parametrize('fn', (validate_config_main, validate_manifest_main))
 def test_mains_not_ok(tmpdir, fn):
     not_yaml = tmpdir.join('f.notyaml')
@@ -261,3 +305,12 @@ def test_minimum_pre_commit_version_failing():
 def test_minimum_pre_commit_version_passing():
     cfg = {'repos': [], 'minimum_pre_commit_version': '0'}
     cfgv.validate(cfg, CONFIG_SCHEMA)
+
+
+@pytest.mark.parametrize('schema', (CONFIG_SCHEMA, CONFIG_REPO_DICT))
+def test_warn_additional(schema):
+    allowed_keys = {item.key for item in schema.items if hasattr(item, 'key')}
+    warn_additional, = [
+        x for x in schema.items if isinstance(x, cfgv.WarnAdditionalKeys)
+    ]
+    assert allowed_keys == set(warn_additional.keys)