diff --git a/python/selfie-lib/selfie_lib/CommentTracker.py b/python/selfie-lib/selfie_lib/CommentTracker.py index e8ee1bf0..8e23e0c5 100644 --- a/python/selfie-lib/selfie_lib/CommentTracker.py +++ b/python/selfie-lib/selfie_lib/CommentTracker.py @@ -1,19 +1,11 @@ -from typing import Dict, Iterable, Tuple +from typing import Dict, Iterable, Tuple, Sequence, TypeVar, Callable +from abc import ABC, abstractmethod from enum import Enum, auto import threading -from selfie_lib.TypedPath import TypedPath -from selfie_lib.Slice import Slice - - -# Placeholder implementations for CallStack, SnapshotFileLayout, and FS -class CallStack: - pass - -class SnapshotFileLayout: - def sourcePathForCall(self, location) -> "TypedPath": - # Placeholder return or raise NotImplementedError - raise NotImplementedError("sourcePathForCall is not implemented") +from selfie_lib.Slice import Slice +from selfie_lib.TypedPath import TypedPath +from selfie_lib.WriteTracker import CallStack, SnapshotFileLayout class WritableComment(Enum): diff --git a/python/selfie-lib/selfie_lib/WriteTracker.py b/python/selfie-lib/selfie_lib/WriteTracker.py index 37e2cd9b..579a2afd 100644 --- a/python/selfie-lib/selfie_lib/WriteTracker.py +++ b/python/selfie-lib/selfie_lib/WriteTracker.py @@ -1,13 +1,49 @@ -from typing import List, Optional, Generic, TypeVar, Dict -from selfie_lib.CommentTracker import SnapshotFileLayout +from typing import List, Optional, Generic, TypeVar, Dict, cast, Callable, Sequence from abc import ABC, abstractmethod import inspect, threading from functools import total_ordering +from selfie_lib.SourceFile import SourceFile +from selfie_lib.Literals import LiteralValue +from selfie_lib.TypedPath import TypedPath + + T = TypeVar("T") U = TypeVar("U") +class FS(ABC): + @abstractmethod + def file_walk(self, typed_path, walk: Callable[[Sequence["TypedPath"]], T]) -> T: + pass + + def file_read(self, typed_path) -> str: + return self.file_read_binary(typed_path).decode() + + def file_write(self, typed_path, content: str): + self.file_write_binary(typed_path, content.encode()) + + @abstractmethod + def file_read_binary(self, typed_path) -> bytes: + pass + + @abstractmethod + def file_write_binary(self, typed_path, content: bytes): + pass + + @abstractmethod + def assert_failed(self, message: str, expected=None, actual=None) -> Exception: + pass + + +class SnapshotFileLayout: + def __init__(self, fs: FS): + self.fs = fs + + def sourcePathForCall(self, location) -> "TypedPath": + raise NotImplementedError("sourcePathForCall is not implemented") + + @total_ordering class CallLocation: def __init__(self, file_name: Optional[str], line: int): @@ -132,3 +168,34 @@ def recordInternal( class DiskWriteTracker(WriteTracker[T, U]): def record(self, key: T, snapshot: U, call: CallStack, layout: SnapshotFileLayout): super().recordInternal(key, snapshot, call, layout) + + +class InlineWriteTracker(WriteTracker[CallLocation, LiteralValue]): + def record( + self, + key: CallLocation, + snapshot: LiteralValue, + call: CallStack, + layout: SnapshotFileLayout, + ): + super().recordInternal(key, snapshot, call, layout) + + file = layout.sourcePathForCall(key) + if snapshot.expected is not None: + content = SourceFile(file.name, layout.fs.file_read(file)) + try: + snapshot = cast(LiteralValue, snapshot) + parsed_value = content.parse_to_be_like(key.line).parse_literal( + snapshot.format + ) + except Exception as e: + raise AssertionError( + f"Error while parsing the literal at {key.ide_link(layout)}. Please report this error at https://github.com/diffplug/selfie", + e, + ) + if parsed_value != snapshot.expected: + raise layout.fs.assert_failed( + f"Selfie cannot modify the literal at {key.ide_link(layout)} because Selfie has a parsing bug. Please report this error at https://github.com/diffplug/selfie", + snapshot.expected, + parsed_value, + )