Skip to content

Commit

Permalink
Merge branch '42School:master' into fix/#474
Browse files Browse the repository at this point in the history
  • Loading branch information
NiumXp authored Feb 3, 2024
2 parents 18d9e80 + ef5e1d9 commit be303d2
Show file tree
Hide file tree
Showing 14 changed files with 271 additions and 45 deletions.
4 changes: 1 addition & 3 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ Please use markdown to send the code here.


**Additional infos**
- OS:
- python3 --version:
- norminette -v:
Copy and paste the output of `norminette --version` command here.

**Additional context**
Add any other context about the problem here.
2 changes: 1 addition & 1 deletion norminette/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "3.3.54"
__version__ = "3.3.55"
__name__ = "norminette"
__author__ = "42"
__author__email__ = "[email protected]"
18 changes: 17 additions & 1 deletion norminette/__main__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import glob
import sys
import pathlib
import platform
from importlib.metadata import version

import argparse
from norminette.errors import formatters
from norminette.file import File
from norminette.lexer import Lexer, TokenError
from norminette.exceptions import CParsingError
Expand All @@ -13,6 +15,10 @@

import subprocess

version_text = "norminette" + version("norminette")
version_text += f", Python {platform.python_version()}"
version_text += f", {platform.platform()}"


def main():
parser = argparse.ArgumentParser()
Expand All @@ -39,7 +45,7 @@ def main():
"-v",
"--version",
action="version",
version="norminette " + version("norminette"),
version=version_text,
)
parser.add_argument(
"--cfile",
Expand All @@ -61,10 +67,18 @@ def main():
action="store_true",
help="Parse only source files not match to .gitignore",
)
parser.add_argument(
"-f",
"--format",
choices=list(formatter.name for formatter in formatters),
help="formatting style for errors",
default="humanized",
)
parser.add_argument("-R", nargs=1, help="compatibility for norminette 2")
args = parser.parse_args()
registry = Registry()

format = next(filter(lambda it: it.name == args.format, formatters))
files = []
debug = args.debug
if args.cfile or args.hfile:
Expand Down Expand Up @@ -121,6 +135,8 @@ def main():
sys.exit(1)
except KeyboardInterrupt:
sys.exit(1)
errors = format(files)
print(errors)
sys.exit(1 if len(file.errors) else 0)


Expand Down
110 changes: 110 additions & 0 deletions norminette/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from __future__ import annotations

import os
import json
from dataclasses import dataclass, field, asdict
from functools import cmp_to_key
from typing import TYPE_CHECKING, Sequence, Union, Literal, Optional, List

from norminette.norm_error import NormError, NormWarning, errors as errors_dict

if TYPE_CHECKING:
from norminette.file import File


def sort_errs(a: Error, b: Error):
# TODO Add to Error and Highlight dataclasses be sortable to remove this fn
ah: Highlight = a.highlights[0]
bh: Highlight = b.highlights[0]
if ah.column == bh.column and ah.lineno == bh.lineno:
return 1 if a.name > b.name else -1
return ah.column - bh.column if ah.lineno == bh.lineno else ah.lineno - bh.lineno


@dataclass
class Highlight:
lineno: int
column: int
length: Optional[int] = field(default=None)
hint: Optional[str] = field(default=None)


@dataclass
class Error:
name: str
text: str
level: Literal["Error", "Notice"]
highlights: List[Highlight]


class Errors:
__slots__ = "_inner"

def __init__(self) -> None:
self._inner = []

def __len__(self) -> int:
return len(self._inner)

def __iter__(self):
self._inner.sort(key=cmp_to_key(sort_errs))
return iter(self._inner)

# TODO Add `add(...)` method to allow creating `Highlight`s and `Error`s easily

@property
def status(self) -> Literal["OK", "Error"]:
return "OK" if all(it.level == "Notice" for it in self._inner) else "Error"

def append(self, value: Union[NormError, NormWarning]) -> None:
# TODO Remove NormError and NormWarning since it does not provide `length` data
assert isinstance(value, (NormError, NormWarning))
level = "Error" if isinstance(value, NormError) else "Notice"
value = Error(value.errno, value.error_msg, level, highlights=[
Highlight(value.line, value.col, None),
])
self._inner.append(value)


class _formatter:
def __init__(self, files: Union[File, Sequence[File]]) -> None:
if not isinstance(files, list):
files = [files]
self.files = files

def __init_subclass__(cls) -> None:
cls.name = cls.__name__.rstrip("ErrorsFormatter").lower()


class HumanizedErrorsFormatter(_formatter):
def __str__(self) -> str:
output = ''
for file in self.files:
output += f"{file.basename}: {file.errors.status}!"
for error in file.errors:
brief = errors_dict.get(error.name, "Error not found")
highlight = error.highlights[0]
output += f"\n{error.level}: {error.name:<20} "
output += f"(line: {highlight.lineno:>3}, col: {highlight.column:>3}):\t{brief}"
return output


class JSONErrorsFormatter(_formatter):
def __str__(self):
files = []
for file in self.files:
files.append({
"path": os.path.abspath(file.path),
"status": file.errors.status,
"errors": tuple(map(asdict, file.errors)),
})
output = {
"files": files,
}
return json.dumps(output, separators=",:")


formatters = (
JSONErrorsFormatter,
HumanizedErrorsFormatter,
)
37 changes: 2 additions & 35 deletions norminette/file.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,7 @@
import os
from functools import cmp_to_key
from typing import Optional, Union, Literal
from typing import Optional

from norminette.norm_error import NormError, NormWarning


def sort_errs(a, b):
if a.col == b.col and a.line == b.line:
return 1 if a.errno > b.errno else -1
return a.col - b.col if a.line == b.line else a.line - b.line


class Errors:
__slots__ = "_inner"

def __init__(self) -> None:
self._inner = []

def __len__(self) -> int:
return len(self._inner)

def __iter__(self):
self._inner.sort(key=cmp_to_key(sort_errs))
return iter(self._inner)

@property
def status(self) -> Literal["OK", "Error"]:
if not self:
return "OK"
if all(isinstance(it, NormWarning) for it in self):
return "OK"
return "Error"

def append(self, value: Union[NormError, NormWarning]) -> None:
assert isinstance(value, (NormError, NormWarning))
self._inner.append(value)
from norminette.errors import Errors


class File:
Expand Down
3 changes: 0 additions & 3 deletions norminette/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,3 @@ def run(self, context):
print(context.debug)
if context.debug > 0:
print("uncaught ->", unrecognized_tkns)
print(f"{context.file.basename}: {context.file.errors.status}!")
for error in context.file.errors:
print(error)
1 change: 1 addition & 0 deletions norminette/rules/check_func_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def run(self, context):
i += 1
if context.check_token(i, "LPARENTHESIS") is False:
context.new_error("EXP_PARENTHESIS", context.peek_token(i))
i = context.skip_ws(i)
i += 1
deep = 1
while deep > 0 and context.peek_token(i) is not None:
Expand Down
3 changes: 3 additions & 0 deletions norminette/rules/is_preprocessor_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import contextlib

from norminette.rules import Rule, Primary
from norminette.lexer.dictionary import keywords
from norminette.exceptions import CParsingError
from norminette.context import Macro

Expand Down Expand Up @@ -45,6 +46,8 @@
"DOT",
"SPACE",
"TAB",
# TODO Remove all keyword tokens and add to just use 'IDENTIFIER' instead
*keywords.values(), # https://github.com/42School/norminette/issues/470
)


Expand Down
3 changes: 3 additions & 0 deletions tests/rules/rules_generator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from norminette.lexer import Lexer
from norminette.context import Context
from norminette.registry import Registry
from norminette.errors import HumanizedErrorsFormatter


registry = Registry()
Expand All @@ -23,6 +24,8 @@ def test_rule_for_file(file, capsys):
lexer = Lexer(file)
context = Context(file, lexer.get_tokens(), debug=2)
registry.run(context)
errors = HumanizedErrorsFormatter(file)
print(errors)
captured = capsys.readouterr()

assert captured.out == out_content
12 changes: 11 additions & 1 deletion tests/rules/samples/check_preprocessor_include.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,14 @@ void main(void);

#if 1
# include "ok but not ok.h"
#endif
#endif

#include <float.h>
#include <int.h>
#include <char.h>
#include <wchar.h>
#include <if.h>
#include <else.h>
#include <bool.h>
#include <null.h>
#include <NULL.h>
31 changes: 30 additions & 1 deletion tests/rules/samples/check_preprocessor_include.out
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,40 @@
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 11":
<HASH> <SPACE> <IDENTIFIER=include> <SPACE> <STRING="ok but not ok.h"> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 12":
<HASH> <IDENTIFIER=endif>
<HASH> <IDENTIFIER=endif> <NEWLINE>
check_preprocessor_include.c - IsEmptyLine In "GlobalScope" from "None" line 13":
<NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 14":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <FLOAT> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 15":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <INT> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 16":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <CHAR> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 17":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <IDENTIFIER=wchar> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 18":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <IF> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 19":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <ELSE> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 20":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <IDENTIFIER=bool> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 21":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <IDENTIFIER=null> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c - IsPreprocessorStatement In "GlobalScope" from "None" line 22":
<HASH> <IDENTIFIER=include> <SPACE> <LESS_THAN> <NULL> <DOT> <IDENTIFIER=h> <MORE_THAN> <NEWLINE>
check_preprocessor_include.c: Error!
Error: INVALID_HEADER (line: 1, col: 1): Missing or invalid 42 header
Error: INCLUDE_HEADER_ONLY (line: 2, col: 10): .c file includes are forbidden
Error: INCLUDE_HEADER_ONLY (line: 3, col: 10): .c file includes are forbidden
Error: CONSECUTIVE_NEWLINES (line: 5, col: 1): Consecutive newlines
Error: INCLUDE_START_FILE (line: 8, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 11, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 14, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 15, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 16, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 17, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 18, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 19, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 20, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 21, col: 1): Include must be at the start of file
Error: INCLUDE_START_FILE (line: 22, col: 1): Include must be at the start of file
12 changes: 12 additions & 0 deletions tests/rules/samples/ko_func_name2.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,15 @@ int main (void)
{
return (21);
}

int main (void)
{
int array[] = {1, 2, 3, 4, 5};

return (0);
}

int _1 a b c(void)
{
return ;
}
33 changes: 33 additions & 0 deletions tests/rules/samples/ko_func_name2.out
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,40 @@
<TAB> <RETURN> <SPACE> <LPARENTHESIS> <CONSTANT=21> <RPARENTHESIS> <SEMI_COLON> <NEWLINE>
ko_func_name2.c - IsBlockEnd In "Function" from "GlobalScope" line 9":
<RBRACE> <NEWLINE>
ko_func_name2.c - IsEmptyLine In "GlobalScope" from "None" line 10":
<NEWLINE>
ko_func_name2.c - IsFuncDeclaration In "GlobalScope" from "None" line 11":
<INT> <SPACE> <IDENTIFIER=main> <SPACE> <LPARENTHESIS> <VOID> <RPARENTHESIS> <NEWLINE>
ko_func_name2.c - IsBlockStart In "Function" from "GlobalScope" line 12":
<LBRACE> <NEWLINE>
ko_func_name2.c - IsVarDeclaration In "Function" from "GlobalScope" line 13":
<SPACE> <SPACE> <SPACE> <SPACE> <INT> <SPACE> <IDENTIFIER=array> <LBRACKET> <RBRACKET> <SPACE> <ASSIGN> <SPACE> <LBRACE> <CONSTANT=1> <COMMA> <SPACE> <CONSTANT=2> <COMMA> <SPACE> <CONSTANT=3> <COMMA> <SPACE> <CONSTANT=4> <COMMA> <SPACE> <CONSTANT=5> <RBRACE> <SEMI_COLON> <NEWLINE>
ko_func_name2.c - IsEmptyLine In "Function" from "GlobalScope" line 14":
<NEWLINE>
ko_func_name2.c - IsExpressionStatement In "Function" from "GlobalScope" line 15":
<SPACE> <SPACE> <SPACE> <SPACE> <RETURN> <SPACE> <LPARENTHESIS> <CONSTANT=0> <RPARENTHESIS> <SEMI_COLON> <NEWLINE>
ko_func_name2.c - IsBlockEnd In "Function" from "GlobalScope" line 16":
<RBRACE> <NEWLINE>
ko_func_name2.c - IsEmptyLine In "GlobalScope" from "None" line 17":
<NEWLINE>
ko_func_name2.c - IsFuncDeclaration In "GlobalScope" from "None" line 18":
<INT> <SPACE> <IDENTIFIER=_1> <SPACE> <IDENTIFIER=a> <SPACE> <IDENTIFIER=b> <SPACE> <IDENTIFIER=c> <LPARENTHESIS> <VOID> <RPARENTHESIS> <NEWLINE>
ko_func_name2.c - IsBlockStart In "Function" from "GlobalScope" line 19":
<LBRACE> <NEWLINE>
ko_func_name2.c - IsExpressionStatement In "Function" from "GlobalScope" line 20":
<TAB> <RETURN> <SPACE> <SEMI_COLON> <NEWLINE>
ko_func_name2.c - IsBlockEnd In "Function" from "GlobalScope" line 21":
<RBRACE> <NEWLINE>
ko_func_name2.c: Error!
Error: INVALID_HEADER (line: 1, col: 1): Missing or invalid 42 header
Error: EXP_PARENTHESIS (line: 1, col: 9): Expected parenthesis
Error: EXP_PARENTHESIS (line: 6, col: 9): Expected parenthesis
Error: SPACE_BEFORE_FUNC (line: 11, col: 4): space before function name
Error: EXP_PARENTHESIS (line: 11, col: 9): Expected parenthesis
Error: TOO_FEW_TAB (line: 13, col: 1): Missing tabs for indent level
Error: SPACE_REPLACE_TAB (line: 13, col: 5): Found space when expecting tab
Error: SPACE_REPLACE_TAB (line: 13, col: 8): Found space when expecting tab
Error: DECL_ASSIGN_LINE (line: 13, col: 17): Declaration and assignation on a single line
Error: TOO_FEW_TAB (line: 15, col: 1): Missing tabs for indent level
Error: SPACE_REPLACE_TAB (line: 15, col: 5): Found space when expecting tab
Error: SPACE_BEFORE_FUNC (line: 18, col: 11): space before function name
Loading

0 comments on commit be303d2

Please sign in to comment.