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

Remove tree from PythonSequentialLinter #3535

Merged
merged 99 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from 89 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
b53e0fd
Let append tree return None
JCZuurmond Jan 15, 2025
108dd7b
Test bidirectionality of appended trees
JCZuurmond Jan 15, 2025
8a22d90
Rename append_tree to attach tree
JCZuurmond Jan 15, 2025
6c0ebcf
Clean test for attaching child tree
JCZuurmond Jan 15, 2025
fd2c761
Rewrite test for module propagation
JCZuurmond Jan 15, 2025
2c46a01
Rewrite test for not implemented error
JCZuurmond Jan 15, 2025
ca30dcf
Test append globals
JCZuurmond Jan 15, 2025
1d22084
Narrow not implemented test
JCZuurmond Jan 15, 2025
48ddf4e
Test appending globals during attach tree
JCZuurmond Jan 15, 2025
1211dda
Refactor `append_globals` to `extend_globals`
JCZuurmond Jan 15, 2025
043fc6b
Test appending nodes sets parent on node
JCZuurmond Jan 15, 2025
b0e39ef
Test appending nodes adds nodes to end of body
JCZuurmond Jan 15, 2025
51358f2
Move append_nodes method up
JCZuurmond Jan 15, 2025
460e88f
Rename append_nodes to attach_nodes
JCZuurmond Jan 15, 2025
357a2e5
Narrow raising not implemented error test
JCZuurmond Jan 15, 2025
d9ffd91
Add docstring for attach nodes
JCZuurmond Jan 15, 2025
a3c023b
Change defining sources in test
JCZuurmond Jan 16, 2025
1e7a4b9
Update constructing sources
JCZuurmond Jan 17, 2025
249857e
Test PythonLinter with dummy advices
JCZuurmond Jan 16, 2025
ba04281
Test linting unparsable python code
JCZuurmond Jan 16, 2025
18e9c98
Test sequential linter with dummy advices
JCZuurmond Jan 16, 2025
fb8d6ee
Test linting print(1) sets no globals
JCZuurmond Jan 16, 2025
96ef1ec
Test linting with one global
JCZuurmond Jan 16, 2025
e5365ef
Test linting with two globals
JCZuurmond Jan 16, 2025
aed3b12
Test linting separate code sources separates globals
JCZuurmond Jan 16, 2025
ee148eb
Test appending globals sets global
JCZuurmond Jan 16, 2025
84b5f69
Remove SquentialLinter.make_tree
JCZuurmond Jan 16, 2025
d142f7c
Refactor globals linter to fetch globals from body nodes
JCZuurmond Jan 16, 2025
00aad28
Sort globals for consistent testing
JCZuurmond Jan 16, 2025
4c1e79e
Test dummy DFSA Python collector
JCZuurmond Jan 16, 2025
f860a67
Test dummy used table Python collector
JCZuurmond Jan 16, 2025
906ba87
Delete dead code `PythonSequentialLinter.process_child_cell`
JCZuurmond Jan 16, 2025
feb0a8c
Format imports
JCZuurmond Jan 16, 2025
73991ae
Remove Tree from python sequential linter
JCZuurmond Jan 16, 2025
4cf6d8a
Fix type hinting for classmethod with child classes
JCZuurmond Jan 16, 2025
04fda6f
Let tree loading return failure
JCZuurmond Jan 16, 2025
7e16589
Connect cells using parents
JCZuurmond Jan 16, 2025
d2db4b6
Pass inherited tree to notebook linter
JCZuurmond Jan 17, 2025
14b8c45
Format
JCZuurmond Jan 17, 2025
5c28fc9
Disable test that does not reflect a realistic scenario
JCZuurmond Jan 17, 2025
1b8f4b5
Pass run cell's tree as parent to the notebook it is running
JCZuurmond Jan 17, 2025
b7998bc
Do not append child nodes to parents body
JCZuurmond Jan 17, 2025
70aa5bf
Use type over Type from type hinting
JCZuurmond Jan 17, 2025
c81e6b1
Rename attach_nodes to attach_child_nodes in Python analyzer
JCZuurmond Jan 17, 2025
3fb49a7
Delete test for unrealistic scenario
JCZuurmond Jan 17, 2025
1ff9891
Rename method to parse trees
JCZuurmond Jan 17, 2025
273a40d
Add tests for notebook linter
JCZuurmond Jan 17, 2025
7728719
Test for a table migration deprecation advice to be given
JCZuurmond Jan 17, 2025
772684c
Test for notebook cells to consider only code above
JCZuurmond Jan 17, 2025
2f4c7fa
Test inverse of previous commit
JCZuurmond Jan 17, 2025
5441ec5
Test inverse of reading table from other cell
JCZuurmond Jan 17, 2025
d6c0afc
Format
JCZuurmond Jan 17, 2025
435f6c8
Let PythonSequentialLinter inherit correctly
JCZuurmond Jan 17, 2025
fd8f04d
Remove PythonSequentialLinter initialization from NotebookLinter init
JCZuurmond Jan 17, 2025
7209aac
Test NotebookLinter to lint parse failure
JCZuurmond Jan 17, 2025
f2dbc56
Remove redundant if statement
JCZuurmond Jan 18, 2025
2603d14
Format
JCZuurmond Jan 20, 2025
23e5a49
Rewrite load children from tree
JCZuurmond Jan 20, 2025
8cef0ef
Remove redundant for-loops
JCZuurmond Jan 20, 2025
82f3ad1
Remove unused name method
JCZuurmond Jan 20, 2025
ce23e4f
Move load tree from run cell up
JCZuurmond Jan 20, 2025
7176276
Rename methods for consistency
JCZuurmond Jan 20, 2025
768706c
Rename Python tree cache for clarity
JCZuurmond Jan 20, 2025
1f44031
Always cache Python trees
JCZuurmond Jan 20, 2025
b80f2d3
Rename methods for clarity
JCZuurmond Jan 20, 2025
54895fb
Rename variable
JCZuurmond Jan 20, 2025
3a8bb37
Add docstrings
JCZuurmond Jan 20, 2025
f46955e
Remove redundant tree initialization
JCZuurmond Jan 20, 2025
be03c3b
Return failures for each notebook cell
JCZuurmond Jan 20, 2025
3646140
Fix expected start and end line
JCZuurmond Jan 20, 2025
b799076
Fix type hint
JCZuurmond Jan 20, 2025
da3eb20
Move from_source_code class method to tester class
JCZuurmond Jan 20, 2025
cf5679d
Change elif to if
JCZuurmond Jan 20, 2025
63a5d22
Merge branch 'main' into fix/remove-tree-from-python-sequential-linter
JCZuurmond Jan 21, 2025
8113828
Rename inherited tree to parent tree
JCZuurmond Jan 21, 2025
ae206b8
Test creating run cell from notebook
JCZuurmond Jan 21, 2025
4b1528f
Test infer value from parent's child
JCZuurmond Jan 21, 2025
5b98b44
Test Python trees simulating notebook running other notebook
JCZuurmond Jan 21, 2025
2e7663f
Test inferring value from grand parent
JCZuurmond Jan 21, 2025
9701f17
Test using variable from ran child notebook
JCZuurmond Jan 21, 2025
02e8cb3
Test infer from parent using extend globals
JCZuurmond Jan 21, 2025
6f8a819
Test infer from grand parent using extend globals
JCZuurmond Jan 21, 2025
47e3273
Fix test name
JCZuurmond Jan 21, 2025
e800b7e
Test inferring from sibling tree
JCZuurmond Jan 21, 2025
b648b2f
Test simulate using value from child notebook
JCZuurmond Jan 21, 2025
25549ec
Test simulate using value from parent notebook
JCZuurmond Jan 21, 2025
09470e6
Test propagating module with extend globals
JCZuurmond Jan 21, 2025
2a16d90
Let NotebookLinter fail early while parsing
JCZuurmond Jan 21, 2025
5bebad4
Rewrite notebook linter to only extend globals
JCZuurmond Jan 21, 2025
f25174a
Add test showing unresolvable node issue
JCZuurmond Jan 22, 2025
0c67f3e
Pass tree globals to next cells tree
JCZuurmond Jan 22, 2025
a273997
Merge branch 'main' into fix/remove-tree-from-python-sequential-linter
JCZuurmond Jan 22, 2025
6360e7b
Add assumption to docstring
JCZuurmond Jan 23, 2025
54607a6
Merge branch 'main' into fix/remove-tree-from-python-sequential-linter
JCZuurmond Jan 28, 2025
4afb7e9
Add new line to test sources
JCZuurmond Jan 28, 2025
2c0f32b
Get first advice with next
JCZuurmond Jan 28, 2025
c5a7f28
Let process code node return failure
JCZuurmond Jan 28, 2025
02e2254
Merge branch 'main' into fix/remove-tree-from-python-sequential-linter
JCZuurmond Jan 31, 2025
35285e3
Merge branch 'main' into fix/remove-tree-from-python-sequential-linter
JCZuurmond Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/databricks/labs/ucx/source_code/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, BinaryIO, TextIO
from typing import Any, BinaryIO, TextIO, TypeVar

from astroid import NodeNG # type: ignore
from sqlglot import Expression, parse as parse_sql
Expand Down Expand Up @@ -40,6 +40,9 @@
logger = logging.getLogger(__name__)


T = TypeVar("T", bound="Advice")


@dataclass
class Advice:
code: str
Expand All @@ -66,7 +69,7 @@ def for_path(self, path: Path) -> LocatedAdvice:
return LocatedAdvice(self, path)

@classmethod
def from_node(cls, *, code: str, message: str, node: NodeNG) -> Advice:
def from_node(cls: type[T], *, code: str, message: str, node: NodeNG) -> T:
# Astroid lines are 1-based.
return cls(
code=code,
Expand Down
248 changes: 114 additions & 134 deletions src/databricks/labs/ucx/source_code/notebooks/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,17 @@
from pathlib import Path
from typing import cast

from astroid import Module, NodeNG # type: ignore

from databricks.sdk.service.workspace import Language

from databricks.labs.ucx.hive_metastore.table_migration_status import TableMigrationIndex
from databricks.labs.ucx.source_code.base import (
file_language,
is_a_notebook,
safe_read_text,
read_text,
Advice,
Advisory,
CurrentSessionState,
Failure,
Linter,
)

from databricks.labs.ucx.source_code.graph import (
Expand All @@ -36,7 +32,7 @@
UnresolvedPath,
)
from databricks.labs.ucx.source_code.notebooks.magic import MagicLine
from databricks.labs.ucx.source_code.python.python_ast import Tree, NodeBase, PythonSequentialLinter
from databricks.labs.ucx.source_code.python.python_ast import MaybeTree, PythonLinter, Tree
from databricks.labs.ucx.source_code.notebooks.cells import (
CellLanguage,
Cell,
Expand Down Expand Up @@ -129,163 +125,162 @@ class NotebookLinter:
to the code cells according to the language of the cell.
"""

@classmethod
def from_source(
Copy link
Member Author

Choose a reason for hiding this comment

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

This was only used by tests, it should not be the way to create the notebook linter in UCX

cls,
index: TableMigrationIndex,
path_lookup: PathLookup,
session_state: CurrentSessionState,
source: str,
default_language: Language,
) -> NotebookLinter:
ctx = LinterContext(index)
notebook = Notebook.parse(Path(""), source, default_language)
assert notebook is not None
return cls(ctx, path_lookup, session_state, notebook)

def __init__(
self,
context: LinterContext,
path_lookup: PathLookup,
session_state: CurrentSessionState,
notebook: Notebook,
inherited_tree: Tree | None = None,
parent_tree: Tree | None = None,
):
self._context: LinterContext = context
self._path_lookup = path_lookup
self._session_state = session_state
self._notebook: Notebook = notebook
# reuse Python linter across related files and notebook cells
# this is required in order to accumulate statements for improved inference
self._python_linter: PythonSequentialLinter = cast(PythonSequentialLinter, context.linter(Language.PYTHON))
if inherited_tree is not None:
self._python_linter.append_tree(inherited_tree)
self._python_trees: dict[PythonCell, Tree] = {} # the original trees to be linted
self._parent_tree = parent_tree or Tree.new_module()

# Python trees are constructed during notebook parsing and cached for later usage
self._python_tree_cache: dict[tuple[Path, Cell], Tree] = {} # Path in key is the notebook's path

def lint(self) -> Iterable[Advice]:
has_failure = False
for advice in self._load_tree_from_notebook(self._notebook, True):
if isinstance(advice, Failure): # happens when a cell is unparseable
has_failure = True
yield advice
if has_failure:
maybe_tree = self._parse_notebook(self._notebook, parent_tree=self._parent_tree)
if maybe_tree.failure:
yield maybe_tree.failure
return
for cell in self._notebook.cells:
if not self._context.is_supported(cell.language.language):
try:
Copy link
Member Author

Choose a reason for hiding this comment

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

This is why: #3544

linter = self._context.linter(cell.language.language)
except ValueError: # Language is not supported (yet)
continue
if isinstance(cell, PythonCell):
tree = self._python_trees[cell]
advices = self._python_linter.lint_tree(tree)
linter = cast(PythonLinter, linter)
tree = self._python_tree_cache[(self._notebook.path, cell)]
advices = linter.lint_tree(tree)
else:
linter = self._linter(cell.language.language)
advices = linter.lint(cell.original_code)
for advice in advices:
yield dataclasses.replace(
advice,
start_line=advice.start_line + cell.original_offset,
end_line=advice.end_line + cell.original_offset,
)
return
JCZuurmond marked this conversation as resolved.
Show resolved Hide resolved

def _load_tree_from_notebook(self, notebook: Notebook, register_trees: bool) -> Iterable[Advice]:
def _parse_notebook(self, notebook: Notebook, *, parent_tree: Tree) -> MaybeTree:
"""Parse a notebook by parsing its cells.

Returns :
MaybeTree : The tree or failure belonging to the **last** cell.
"""
maybe_tree = MaybeTree(None, None)
JCZuurmond marked this conversation as resolved.
Show resolved Hide resolved
for cell in notebook.cells:
if isinstance(cell, RunCell):
yield from self._load_tree_from_run_cell(cell)
continue
if isinstance(cell, PythonCell):
yield from self._load_tree_from_python_cell(cell, register_trees)
continue

def _load_tree_from_python_cell(self, python_cell: PythonCell, register_trees: bool) -> Iterable[Advice]:
maybe_tree = self._resolve_and_parse_run_cell(cell, parent_tree=parent_tree)
elif isinstance(cell, PythonCell):
maybe_tree = self._parse_python_cell(cell)
if maybe_tree.failure:
return maybe_tree
if maybe_tree.tree:
# The subsequent cell gets the globals from the previous cell
maybe_tree.tree.extend_globals(parent_tree.node.globals)
self._python_tree_cache[(notebook.path, cell)] = maybe_tree.tree
parent_tree = maybe_tree.tree
return maybe_tree

def _resolve_and_parse_run_cell(self, run_cell: RunCell, *, parent_tree: Tree) -> MaybeTree:
"""Resolve the path in the run cell and parse the notebook it refers."""
path = run_cell.maybe_notebook_path()
if path is None:
return MaybeTree(None, None) # malformed run cell already reported
notebook = self._resolve_and_parse_notebook_path(path)
if not notebook:
return MaybeTree(None, None)
JCZuurmond marked this conversation as resolved.
Show resolved Hide resolved
maybe_tree = self._parse_notebook(notebook, parent_tree=parent_tree)
if maybe_tree.tree:
# From the perspective of this cell, a run cell pulls the globals from the child notebook in
parent_tree.extend_globals(maybe_tree.tree.node.globals)
return maybe_tree

def _parse_python_cell(self, python_cell: PythonCell) -> MaybeTree:
"""Parse the Python cell."""
maybe_tree = Tree.maybe_normalized_parse(python_cell.original_code)
if maybe_tree.failure:
yield maybe_tree.failure
tree = maybe_tree.tree
# a cell with only comments will not produce a tree
if register_trees:
self._python_trees[python_cell] = tree or Tree.new_module()
if not tree:
return
yield from self._load_children_from_tree(tree)

def _load_children_from_tree(self, tree: Tree) -> Iterable[Advice]:
assert isinstance(tree.node, Module)
# look for child notebooks (and sys.path changes that might affect their loading)
base_nodes: list[NodeBase] = []
base_nodes.extend(self._list_run_magic_lines(tree))
base_nodes.extend(SysPathChange.extract_from_tree(self._session_state, tree))
if len(base_nodes) == 0:
self._python_linter.append_tree(tree)
return
# append globals
globs = cast(Module, tree.node).globals
self._python_linter.append_globals(globs)
# need to execute things in intertwined sequence so concat and sort them
nodes = list(cast(Module, tree.node).body)
base_nodes = sorted(base_nodes, key=lambda node: (node.node.lineno, node.node.col_offset))
yield from self._load_children_with_base_nodes(nodes, base_nodes)
# append remaining nodes
self._python_linter.append_nodes(nodes)
failure = dataclasses.replace(
maybe_tree.failure,
start_line=maybe_tree.failure.start_line + python_cell.original_offset,
end_line=maybe_tree.failure.end_line + python_cell.original_offset,
)
return MaybeTree(None, failure)
assert maybe_tree.tree is not None
JCZuurmond marked this conversation as resolved.
Show resolved Hide resolved
maybe_child_tree = self._parse_tree(maybe_tree.tree)
if maybe_child_tree.failure:
return maybe_child_tree
return maybe_tree

def _parse_tree(self, tree: Tree) -> MaybeTree:
"""Parse tree by looking for referred notebooks and path changes that might affect loading notebooks."""
code_path_nodes = self._list_magic_lines_with_run_command(tree) + SysPathChange.extract_from_tree(
self._session_state, tree
)
maybe_tree = MaybeTree(None, None)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should also be:

Suggested change
maybe_tree = MaybeTree(None, None)
maybe_tree: MaybeTree

That will then let the linter expose a problem: if there are no code_path_nodes to iterate over then we return an invalid instance.

What should we return in that case? (Can it be ruled out… do we always have nodes to iterate over?)

Finally, why do we return the last tree? Is it somehow more important than any earlier ones? (If we don't care, why return it? In that case the return type should probably be Failure | 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.

On your first point, I updated the signature to be MaybeTree | None, which works - for now.

On the "why do we return the last tree", that logic is t.b.d.. At least, this PR resolves a bug with the notebook linting and adds (much) more unit tests. At the same time, I do not know exactly (yet) how the Python trees should be linked/merged for all to work as expected.

So, currently, it returns the last tree as the trees are sort of chained, e.g. the second notebook can go "up" to the first cell and the third cell can go "up" to the second (and via the second cell "find" the first cell). It becomes more complicated when introducing new notebooks with the run magic. That is the part I am not sure about yet, but I am more confident than before this PR as it introduces more tests

# Sys path changes require to load children in order of reading
for base_node in sorted(code_path_nodes, key=lambda node: (node.node.lineno, node.node.col_offset)):
maybe_tree = self._process_code_node(base_node, parent_tree=tree)
if maybe_tree.failure:
return maybe_tree
return maybe_tree

@staticmethod
def _list_run_magic_lines(tree: Tree) -> Iterable[MagicLine]:

def _ignore_problem(_code: str, _message: str, _node: NodeNG) -> None:
return None

commands, _ = MagicLine.extract_from_tree(tree, _ignore_problem)
for command in commands:
if isinstance(command.as_magic(), RunCommand):
yield command

def _load_children_with_base_nodes(self, nodes: list[NodeNG], base_nodes: list[NodeBase]) -> Iterable[Advice]:
for base_node in base_nodes:
yield from self._load_children_with_base_node(nodes, base_node)

def _load_children_with_base_node(self, nodes: list[NodeNG], base_node: NodeBase) -> Iterable[Advice]:
while len(nodes) > 0:
node = nodes.pop(0)
self._python_linter.append_nodes([node])
if node.lineno < base_node.node.lineno:
continue
yield from self._load_children_from_base_node(base_node)

def _load_children_from_base_node(self, base_node: NodeBase) -> Iterable[Advice]:
if isinstance(base_node, SysPathChange):
yield from self._mutate_path_lookup(base_node)
return
if isinstance(base_node, MagicLine):
magic = base_node.as_magic()
def _list_magic_lines_with_run_command(tree: Tree) -> list[MagicLine]:
"""List the magic lines with a run command"""
run_commands = []
magic_lines, _ = MagicLine.extract_from_tree(tree, lambda code, message, node: None)
for magic_line in magic_lines:
if isinstance(magic_line.as_magic(), RunCommand):
run_commands.append(magic_line)
return run_commands

def _process_code_node(self, node: SysPathChange | MagicLine, *, parent_tree: Tree) -> MaybeTree:
"""Process a code node.

1. `SysPathChange` mutate the path lookup.
2. `MagicLine` containing a `RunCommand` run other notebooks that should be parsed.
"""
if isinstance(node, SysPathChange):
failure = self._mutate_path_lookup(node)
if failure:
return MaybeTree(None, failure)
if isinstance(node, MagicLine):
magic = node.as_magic()
assert isinstance(magic, RunCommand)
notebook = self._load_source_from_path(magic.notebook_path)
notebook = self._resolve_and_parse_notebook_path(magic.notebook_path)
if notebook is None:
yield Advisory.from_node(
failure = Failure.from_node(
code='dependency-not-found',
message=f"Can't locate dependency: {magic.notebook_path}",
node=base_node.node,
node=node.node,
)
return
yield from self._load_tree_from_notebook(notebook, False)
return

def _mutate_path_lookup(self, change: SysPathChange) -> Iterable[Advice]:
return MaybeTree(None, failure)
maybe_tree = self._parse_notebook(notebook, parent_tree=parent_tree)
if maybe_tree.tree:
# From the perspective of this node, a run node pulls the globals from the child notebook in
parent_tree.extend_globals(maybe_tree.tree.node.globals)
return maybe_tree
return MaybeTree(None, None)
JCZuurmond marked this conversation as resolved.
Show resolved Hide resolved

def _mutate_path_lookup(self, change: SysPathChange) -> Failure | None:
"""Mutate the path lookup."""
if isinstance(change, UnresolvedPath):
yield Advisory.from_node(
return Failure.from_node(
code='sys-path-cannot-compute-value',
message=f"Can't update sys.path from {change.node.as_string()} because the expression cannot be computed",
node=change.node,
)
return
change.apply_to(self._path_lookup)
return None

def _load_tree_from_run_cell(self, run_cell: RunCell) -> Iterable[Advice]:
path = run_cell.maybe_notebook_path()
if path is None:
return # malformed run cell already reported
notebook = self._load_source_from_path(path)
if notebook is not None:
yield from self._load_tree_from_notebook(notebook, False)

def _load_source_from_path(self, path: Path | None) -> Notebook | None:
def _resolve_and_parse_notebook_path(self, path: Path | None) -> Notebook | None:
"""Resolve and parse notebook path."""
if path is None:
return None # already reported during dependency building
resolved = self._path_lookup.resolve(path)
Expand All @@ -302,15 +297,6 @@ def _load_source_from_path(self, path: Path | None) -> Notebook | None:
return None
return Notebook.parse(path, source, language)

def _linter(self, language: Language) -> Linter:
if language is Language.PYTHON:
return self._python_linter
return self._context.linter(language)

@staticmethod
def name() -> str:
return "notebook-linter"


class FileLinter:
_NOT_YET_SUPPORTED_SUFFIXES = {
Expand Down Expand Up @@ -417,8 +403,6 @@ def _lint_file(self) -> Iterable[Advice]:
else:
try:
linter = self._ctx.linter(language)
if self._inherited_tree is not None and isinstance(linter, PythonSequentialLinter):
linter.append_tree(self._inherited_tree)
yield from linter.lint(self._content)
except ValueError as err:
failure_message = f"Error while parsing content of {self._path.as_posix()}: {err}"
Expand All @@ -432,10 +416,6 @@ def _lint_notebook(self) -> Iterable[Advice]:
return
notebook = Notebook.parse(self._path, self._content, language)
notebook_linter = NotebookLinter(
self._ctx,
self._path_lookup,
self._session_state,
notebook,
self._inherited_tree,
self._ctx, self._path_lookup, self._session_state, notebook, self._inherited_tree
)
yield from notebook_linter.lint()
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def build_inherited_context(self, child_path: Path) -> InheritedContext:
# append nodes
node_line = base_node.node.lineno
nodes = tree.nodes_between(last_line + 1, node_line - 1)
context.tree.attach_nodes(nodes)
context.tree.attach_child_nodes(nodes)
globs = tree.globals_between(last_line + 1, node_line - 1)
context.tree.extend_globals(globs)
last_line = node_line
Expand All @@ -86,7 +86,7 @@ def build_inherited_context(self, child_path: Path) -> InheritedContext:
assert context.tree is not None, "Tree should be initialized"
if last_line < line_count:
nodes = tree.nodes_between(last_line + 1, line_count)
context.tree.attach_nodes(nodes)
context.tree.attach_child_nodes(nodes)
globs = tree.globals_between(last_line + 1, line_count)
context.tree.extend_globals(globs)
return context
Expand Down
Loading
Loading