Repository: sqlfluff/sqlfluff

Issue:
"ValueError: Position Not Found" with macro spanning entire file
## Expected Behaviour

`sqlfluff parse` should probably not fail with an exception and stack trace.

## Observed Behaviour

`sqlfluff parse` throws an exception, given an input file which is entirely spanned by a Jinja macro.

## Steps to Reproduce

```console
$ echo -n '{% macro foo() %}{% endmacro %}' | sqlfluff parse -
Traceback (most recent call last):
  File "/home/vladimir/work/extern/sqlfluff/venv/bin/sqlfluff", line 33, in <module>
    sys.exit(load_entry_point('sqlfluff', 'console_scripts', 'sqlfluff')())
  File "/home/vladimir/work/extern/sqlfluff/venv/lib/python3.9/site-packages/click/core.py", line 1137, in __call__
    return self.main(*args, **kwargs)
  File "/home/vladimir/work/extern/sqlfluff/venv/lib/python3.9/site-packages/click/core.py", line 1062, in main
    rv = self.invoke(ctx)
  File "/home/vladimir/work/extern/sqlfluff/venv/lib/python3.9/site-packages/click/core.py", line 1668, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/vladimir/work/extern/sqlfluff/venv/lib/python3.9/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/vladimir/work/extern/sqlfluff/venv/lib/python3.9/site-packages/click/core.py", line 763, in invoke
    return __callback(*args, **kwargs)
  File "/home/vladimir/work/extern/sqlfluff/src/sqlfluff/cli/commands.py", line 701, in parse
    lnt.parse_string(
  File "/home/vladimir/work/extern/sqlfluff/src/sqlfluff/core/linter/linter.py", line 596, in parse_string
    return self.parse_rendered(rendered, recurse=recurse)
  File "/home/vladimir/work/extern/sqlfluff/src/sqlfluff/core/linter/linter.py", line 294, in parse_rendered
    tokens, lvs, config = cls._lex_templated_file(
  File "/home/vladimir/work/extern/sqlfluff/src/sqlfluff/core/linter/linter.py", line 127, in _lex_templated_file
    tokens, lex_vs = lexer.lex(templated_file)
  File "/home/vladimir/work/extern/sqlfluff/src/sqlfluff/core/parser/lexer.py", line 319, in lex
    segments: Tuple[RawSegment, ...] = self.elements_to_segments(
  File "/home/vladimir/work/extern/sqlfluff/src/sqlfluff/core/parser/lexer.py", line 346, in elements_to_segments
    source_slice = templated_file.templated_slice_to_source_slice(
  File "/home/vladimir/work/extern/sqlfluff/src/sqlfluff/core/templaters/base.py", line 319, in templated_slice_to_source_slice
    ts_stop_sf_start, ts_stop_sf_stop = self._find_slice_indices_of_templated_pos(
  File "/home/vladimir/work/extern/sqlfluff/src/sqlfluff/core/templaters/base.py", line 214, in _find_slice_indices_of_templated_pos
    raise ValueError("Position Not Found")
ValueError: Position Not Found
```

Note: the issue does not occur if the file ends with a newline. 

The contents of the macro also doesn't matter.

## Dialect

None specified

## Version
SQLFluff 6011bdbe05669b075045e8127cdf18cc537686d4, Python 3.9.6

## Configuration

None


Discussion:
Hi @CyberShadow, @tunetheweb, what is expected output of
```console
$ echo -n '{% macro foo() %}{% endmacro %}' | sqlfluff parse -
```
?

Probably the same as the input. Definitely not an exception, in any case.

Edit: Whoops, forgot this was a `parse` case. What @tunetheweb said below, then.
For parse we don't return input.

If we add a newline we get this:

```
% echo -n '{% macro foo() %}{% endmacro %}\n' | sqlfluff parse -
[L:  1, P:  1]      |file:
[L:  1, P:  1]      |    [META] placeholder:                                       [Type: 'compound', Raw: '{% macro foo() %}{% endmacro %}']
[L:  1, P: 32]      |    newline:                                                  '\n'
```

So I'd expect the first two lines to be returned if newline isn't given.

Here's some "equivalent" non-SQL that doesn't fail:

```
% echo " " | sqlfluff parse -     
[L:  1, P:  1]      |file:
[L:  1, P:  1]      |    whitespace:                                               ' '
[L:  1, P:  2]      |    newline:                                                  '\n'

% echo "" | sqlfluff parse - 
[L:  1, P:  1]      |file:
[L:  1, P:  1]      |    newline:                                                  '\n'

% echo "--test" | sqlfluff parse -
[L:  1, P:  1]      |file:
[L:  1, P:  1]      |    comment:                                                  '--test'
[L:  1, P:  7]      |    newline:                                                  '\n'
``




Patch:
diff --git a/src/sqlfluff/core/templaters/base.py b/src/sqlfluff/core/templaters/base.py
--- a/src/sqlfluff/core/templaters/base.py
+++ b/src/sqlfluff/core/templaters/base.py
@@ -84,7 +84,8 @@ def __init__(
         is the same as the source view.
         """
         self.source_str = source_str
-        self.templated_str = templated_str or source_str
+        # An empty string is still allowed as the templated string.
+        self.templated_str = source_str if templated_str is None else templated_str
         # If no fname, we assume this is from a string or stdin.
         self.fname = fname
         # Assume that no sliced_file, means the file is not templated
@@ -297,7 +298,7 @@ def templated_slice_to_source_slice(
                     break
 
         subslices = self.sliced_file[
-            # Ver inclusive slice
+            # Very inclusive slice
             min(ts_start_sf_start, ts_stop_sf_start) : max(
                 ts_start_sf_stop, ts_stop_sf_stop
             )


Test patch:
diff --git a/test/core/templaters/jinja_test.py b/test/core/templaters/jinja_test.py
--- a/test/core/templaters/jinja_test.py
+++ b/test/core/templaters/jinja_test.py
@@ -75,6 +75,18 @@ def test__templater_jinja_error_catatrophic():
     assert len(vs) > 0
 
 
+def test__templater_jinja_lint_empty():
+    """Check that parsing a file which renders to an empty string.
+
+    No exception should be raised, but the parsed tree should be None.
+    """
+    lntr = Linter()
+    parsed = lntr.parse_string(in_str='{{ "" }}')
+    assert parsed.templated_file.source_str == '{{ "" }}'
+    assert parsed.templated_file.templated_str == ""
+    assert parsed.tree is None
+
+
 def assert_structure(yaml_loader, path, code_only=True, include_meta=False):
     """Check that a parsed sql file matches the yaml file with the same name."""
     lntr = Linter()