Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

L009: Handle adding newline after {% endif %} at end of file #2862

Conversation

barrywhart
Copy link
Member

@barrywhart barrywhart commented Mar 14, 2022

Brief summary of the change made

Fixes #2822

OMG, this is the deepest bug I've seen in a long time. Fixing it has required changes to:

  • Core lexer (placeholder generation)
  • Core parser: How BaseSegment generates patches
  • Core linter (patch application)
  • Updating JinjaTracer with better handling of {% endif %} and {% endfor %}
  • Updating is_final_segment() to optionally consider meta segments and updating L009 to use it

Are there any other side effects of this change that we should be aware of?

Pull Request checklist

  • Please confirm you have completed any of the necessary steps below.

  • Included test cases to demonstrate any code changes, which may be one or more of the following:

    • .yml rule test cases in test/fixtures/rules/std_rule_cases.
    • .sql/.yml parser test cases in test/fixtures/dialects (note YML files can be auto generated with tox -e generate-fixture-yml).
    • Full autofix test cases in test/fixtures/linter/autofix.
    • Other.
  • Added appropriate documentation for the change.

  • Created GitHub issues for any relevant followup/future enhancements if appropriate.

@barrywhart barrywhart marked this pull request as draft March 14, 2022 19:46
@codecov
Copy link

codecov bot commented Mar 14, 2022

Codecov Report

Merging #2862 (e49531d) into main (447ecf8) will not change coverage.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff            @@
##              main     #2862   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          163       163           
  Lines        12441     12458   +17     
=========================================
+ Hits         12441     12458   +17     
Impacted Files Coverage Δ
src/sqlfluff/core/linter/common.py 100.00% <ø> (ø)
src/sqlfluff/core/linter/linted_file.py 100.00% <100.00%> (ø)
src/sqlfluff/core/parser/lexer.py 100.00% <100.00%> (ø)
src/sqlfluff/core/parser/segments/base.py 100.00% <100.00%> (ø)
src/sqlfluff/core/rules/base.py 100.00% <100.00%> (ø)
src/sqlfluff/core/templaters/slicers/tracer.py 100.00% <100.00%> (ø)
src/sqlfluff/rules/L009.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 447ecf8...e49531d. Read the comment docs.

@barrywhart
Copy link
Member Author

@alanmcruickshank: Tagging you for review if you have time, as this PR touches several core components I haven't worked on before.

@barrywhart barrywhart marked this pull request as ready for review March 14, 2022 21:45

def dedupe_tuple(self):
"""Generate a tuple of this fix for deduping."""
return (self.source_slice, self.fixed_raw)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this class so BaseSegment can use it.

# in which case, we should skip this patch.
continue
source_slice = getattr(patch, "source_slice", None)
if source_slice is None:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compare this section with "Hide whitespace" enabled


def dedupe_tuple(self):
"""Generate a tuple of this fix for deduping."""
return (self.source_slice, self.fixed_raw)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this class here from src/sqlfluff/core/linter/common.py.

@@ -289,7 +289,6 @@ def _slice_template(self) -> List[RawFileSlice]:
# parts of the tag at a time.
unique_alternate_id = None
alternate_code = None
trimmed_content = ""
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple cleanup: This variable was unused.

Copy link
Member

@alanmcruickshank alanmcruickshank left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty good. The bulk of the structure makes total sense 👍 .

The bits that I think could be a little neater is the iterplay between segments.base.iter_patches() (which we've edited to also return EnrichedPatch) and linter.linted_file.LintedFile.fix_string() (which is the only function which calls it).

Comment on lines +538 to +541
# Generate placeholders for any source-only slices that *follow*
# the last element. This happens, for example, if a Jinja templated
# file ends with "{% endif %}", and there's no trailing newline.
if idx == len(elements) - 1:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is neat. 👍

Very clean and well commented.

Comment on lines 1304 to 1308
# By returning an EnrichedFixPatch (rather than FixPatch), which
# includes a source_slice field, we ensure that fixes adjacent
# to source-only slices (e.g. {% endif %}) are placed
# appropriately relative to source-only slices..
yield EnrichedFixPatch(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a little weird, in that we're enriching some patches and not others - but it seems like a clean solution.

I've checked out the flow around it and I think it makes sense. I do wonder whether we should account more explicitly for potential EnrichedPatch objects being returned in fix_string() where I think in the current flow we might end up recreating them rather than just using the returned ones from iter_patches.

btw - I don't think it would be crazy to pass a TemplatedFile to iter_patches rather than just the templated_string. When it's called in fix_string it's given the latter from the former anyway. If we did that then you'd have access to the strings so you'd be able to populate the details below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into it. I had some of the same thoughts, but kind of suppressed them initially because I was focused on seeing if the whole thing was going to work. 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alanmcruickshank: Done! I also reworked things a bit to make EnrichedFixPatch a subclass of FixPatch. I think it's significantly cleaner now, and was a useful refactoring independent of the this PR.

@tunetheweb tunetheweb changed the title L009: Handle adding newline after {% endif %} at end of file L009: Handle adding newline after {% endif %} at end of file Mar 15, 2022
@barrywhart
Copy link
Member Author

@alanmcruickshank, @tunetheweb: Ready for another review. No change in functionality, just some refactoring of the patch classes and handling in src/sqlfluff/core/parser/segments/base.py and src/sqlfluff/core/linter/linted_file.py.

Copy link
Member

@tunetheweb tunetheweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't spot anything and if Alan's happy then I am too!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

fix keep adding new line on wrong place
3 participants