Repository: alanmcruickshank/sqlfluff

Issue:
Error using diff-quality with deleted files
When running `diff-quality --violations sqlfluff` pre-commit when I have a file staged that has been deleted I get the following error
```
Quality tool not installed: 'sqlfluff'
```

Digging into this a little bit more, the code actually breaks in the sqlfluff linter.py file, specifically in the `paths_from_path` function. It fails on this line because the path doesn't exist locally:
```
if not os.path.exists(path):
```
The OSError that gets raised ends up ultimately getting caught in `diff_quality_tool.py` line 272 and reporting that the quality tool is not installed.

So issue 1 error message isn't helpful and issue 2 is the fact that it's trying to check the quality of a deleted file.

Diving a bit deeper into what's going on, it looks like the diff-cover tool doesn't have any support currently for only listing added / modified files so sqlfluff will always receive the deleted files and fail.

One potential solution here is to add a check for if the file exists in sql_fluff's `diff_quality_plugin.py` file before attempting to lint it.

Let me know what you think of the above, happy to take a crack at the solution if you have an idea on how you want to fix it.

Also quick way to test this yourself, delete a SQL file in an existing repo and before committing it try running `diff-quality --violations sqlfluff` first.

Comments:

Patch:
diff --git a/.gitignore b/.gitignore
index 4899559b..eee1fe42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
-# Ignore VSCode
+# Ignore IDE files
 .vscode
+.idea
 
 # Ignore Python cache and prebuilt things
 .cache
diff --git a/src/sqlfluff/cli/commands.py b/src/sqlfluff/cli/commands.py
index edb81563..8df0a6a9 100644
--- a/src/sqlfluff/cli/commands.py
+++ b/src/sqlfluff/cli/commands.py
@@ -153,7 +153,7 @@ def lint(paths, format, **kwargs):
         # Output the results as we go
         lnt.log(format_linting_result_header(verbose=verbose))
         try:
-            result = lnt.lint_paths(paths, verbosity=verbose)
+            result = lnt.lint_paths(paths, verbosity=verbose, ignore_non_existent_files=False)
         except IOError:
             click.echo(colorize('The path(s) {0!r} could not be accessed. Check it/they exist(s).'.format(paths), 'red'))
             sys.exit(1)
@@ -219,7 +219,7 @@ def fix(force, paths, **kwargs):
     # Lint the paths (not with the fix argument at this stage), outputting as we go.
     lnt.log("==== finding fixable violations ====")
     try:
-        result = lnt.lint_paths(paths, verbosity=verbose, fix=True)
+        result = lnt.lint_paths(paths, verbosity=verbose, fix=True, ignore_non_existent_files=False)
     except IOError:
         click.echo(colorize('The path(s) {0!r} could not be accessed. Check it/they exist(s).'.format(paths), 'red'))
         sys.exit(1)
diff --git a/src/sqlfluff/diff_quality_plugin.py b/src/sqlfluff/diff_quality_plugin.py
index c4c28080..afbd50bb 100644
--- a/src/sqlfluff/diff_quality_plugin.py
+++ b/src/sqlfluff/diff_quality_plugin.py
@@ -25,7 +25,7 @@ class SQLFluffViolationReporter(BaseViolationReporter):
         """
         linter = get_linter(get_config())
         linter.output_func = None
-        linted_path = linter.lint_path(src_path)
+        linted_path = linter.lint_path(src_path, ignore_non_existent_files=True)
         result = []
         for violation in linted_path.get_violations():
             try:
diff --git a/src/sqlfluff/linter.py b/src/sqlfluff/linter.py
index fe01f435..0b93433c 100644
--- a/src/sqlfluff/linter.py
+++ b/src/sqlfluff/linter.py
@@ -800,14 +800,17 @@ class Linter:
         )
         return res
 
-    def paths_from_path(self, path, ignore_file_name='.sqlfluffignore'):
+    def paths_from_path(self, path, ignore_file_name='.sqlfluffignore', ignore_non_existent_files=False):
         """Return a set of sql file paths from a potentially more ambigious path string.
 
         Here we also deal with the .sqlfluffignore file if present.
 
         """
         if not os.path.exists(path):
-            raise IOError("Specified path does not exist")
+            if ignore_non_existent_files:
+                return []
+            else:
+                raise IOError("Specified path does not exist")
 
         # Files referred to exactly are never ignored.
         if not os.path.isdir(path):
@@ -858,11 +861,11 @@ class Linter:
         result.add(linted_path)
         return result
 
-    def lint_path(self, path, verbosity=0, fix=False):
+    def lint_path(self, path, verbosity=0, fix=False, ignore_non_existent_files=False):
         """Lint a path."""
         linted_path = LintedPath(path)
         self.log(format_linting_path(path, verbose=verbosity))
-        for fname in self.paths_from_path(path):
+        for fname in self.paths_from_path(path, ignore_non_existent_files=ignore_non_existent_files):
             config = self.config.make_child_from_path(fname)
             # Handle unicode issues gracefully
             with open(fname, 'r', encoding='utf8', errors='backslashreplace') as target_file:
@@ -872,7 +875,7 @@ class Linter:
                                      fix=fix, config=config))
         return linted_path
 
-    def lint_paths(self, paths, verbosity=0, fix=False):
+    def lint_paths(self, paths, verbosity=0, fix=False, ignore_non_existent_files=False):
         """Lint an iterable of paths."""
         # If no paths specified - assume local
         if len(paths) == 0:
@@ -882,7 +885,8 @@ class Linter:
         for path in paths:
             # Iterate through files recursively in the specified directory (if it's a directory)
             # or read the file directly if it's not
-            result.add(self.lint_path(path, verbosity=verbosity, fix=fix))
+            result.add(self.lint_path(path, verbosity=verbosity, fix=fix,
+                                      ignore_non_existent_files=ignore_non_existent_files))
         return result
 
     def parse_path(self, path, verbosity=0, recurse=True):


Test patch:
diff --git a/test/linter_test.py b/test/linter_test.py
index ba8a0312..841f94c1 100644
--- a/test/linter_test.py
+++ b/test/linter_test.py
@@ -33,6 +33,20 @@ def test__linter__path_from_paths__file():
     assert normalise_paths(paths) == {'test.fixtures.linter.indentation_errors.sql'}
 
 
+def test__linter__path_from_paths__not_exist():
+    """Test extracting paths from a file path."""
+    lntr = Linter(config=FluffConfig())
+    with pytest.raises(IOError):
+        lntr.paths_from_path('asflekjfhsakuefhse')
+
+
+def test__linter__path_from_paths__not_exist_ignore():
+    """Test extracting paths from a file path."""
+    lntr = Linter(config=FluffConfig())
+    paths = lntr.paths_from_path('asflekjfhsakuefhse', ignore_non_existent_files=True)
+    assert len(paths) == 0
+
+
 def test__linter__path_from_paths__dot():
     """Test extracting paths from a dot."""
     lntr = Linter(config=FluffConfig())