Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions src/rstcheck_core/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,35 @@ def check_json(self, source_code: str) -> types.YieldedLintError:
source_origin=self.source_origin, line_number=int(line_number), message=message
)

def check_jsonc(self, source_code: str) -> types.YieldedLintError:
"""Check JSONC (JSON with comments) source for syntax errors.

:param source: JSONC source code to check
:return: :py:obj:`None`
:yield: Found issues
"""
logger.debug("Check JSONC source.")
# strip comments from JSONc source then pass through the JSON validator;
# split and strip full line comments
source_lines = [
line for line in source_code.splitlines() if not line.strip().startswith("//")
]
for ind, line in enumerate(source_lines):
if "//" in line:
source_lines[ind] = line.split("//")[0].strip()
source_code = "\n".join(source_lines)

try:
json.loads(source_code)
except ValueError as exception:
message = f"{exception}"
found = EXCEPTION_LINE_NO_REGEX.search(message)
line_number = int(found.group(1)) if found else 0

yield types.LintError(
source_origin=self.source_origin, line_number=int(line_number), message=message
)

def check_yaml(self, source_code: str) -> types.YieldedLintError:
"""Check YAML source for syntax errors.

Expand Down
72 changes: 72 additions & 0 deletions tests/checker_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,78 @@ def test_check_json_returns_error_on_bad_code_block() -> None:
assert len(result) == 1
assert "Expecting value:" in result[0]["message"]

@staticmethod
def test_check_jsonc_returns_error_on_bad_code_block() -> None:
"""Test ``check_jsonc`` returns error on bad code block."""
source = """
{
"key":
}
"""
cb_checker = checker.CodeBlockChecker("<string>")

result = list(cb_checker.check_jsonc(source))

assert len(result) == 1
assert "Expecting value:" in result[0]["message"]

@staticmethod
def test_check_jsonc_returns_none_on_ok_code_block() -> None:
"""Test ``check_jsonc`` returns ``None`` on ok code block."""
source = """
{
"key": "value"
}
"""
cb_checker = checker.CodeBlockChecker("<string>")

result = list(cb_checker.check_jsonc(source))

assert not result

@staticmethod
def test_check_jsonc_returns_no_error_on_full_line_comment_block() -> None:
"""Test ``check_jsonc`` returns error on bad code block."""
source = """
{
// this key has an annotation
"key": "value"
}
"""
cb_checker = checker.CodeBlockChecker("<string>")

result = list(cb_checker.check_jsonc(source))

assert not result

@staticmethod
def test_check_jsonc_returns_no_error_on_inline_comment_block() -> None:
"""Test ``check_jsonc`` returns error on bad code block."""
source = """
{
"key": "value" // this key has an annotation
}
"""
cb_checker = checker.CodeBlockChecker("<string>")

result = list(cb_checker.check_jsonc(source))

assert not result

@staticmethod
def test_check_jsonc_returns_no_error_in_awkward_inline_comment_block() -> None:
"""Test ``check_jsonc`` returns error on bad code block."""
source = """
{
"key": "value", "next_key": "http://example.org", "//": "another_value", // this key has an annotation
}
"""
cb_checker = checker.CodeBlockChecker("<string>")

result = list(cb_checker.check_jsonc(source))

assert not result

@staticmethod
@pytest.mark.skipif(checker.yaml_imported is False, reason="Requires pyyaml to be installed")
def test_check_yaml_returns_none_on_ok_code_block_no_pyyaml(
Expand Down