Skip to content

Commit

Permalink
Add check for features being included from outside their tree
Browse files Browse the repository at this point in the history
  • Loading branch information
jrha committed Dec 3, 2024
1 parent 658cfe3 commit 371c16c
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 0 deletions.
32 changes: 32 additions & 0 deletions panc/src/main/scripts/panlint/panlint.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from glob import glob
from sys import stdout, exit as sys_exit
from inspect import getmembers, isfunction
from os.path import dirname
import six
from colorama import Fore, Style, init as colorama_init
from prettytable import PrettyTable
Expand Down Expand Up @@ -81,6 +82,9 @@ def diagnose(self):
# Detect whether a file is part of the source tree of a component
RE_COMPONENT_SOURCE_FILE = re.compile(r'^/?(?:\S+/)?(?:core/components/|ncm-)(?P<name>\w+)/\S+$')

# Find where templates belonging to features have been included
RE_FEATURE_INCLUDE = re.compile(r'^\s*[^#]?\s*include.*(?P<name>features/\S+\w)', re.M)

LINE_LENGTH_LIMIT = 120

# Simple regular-expression based checks that will be performed against all non-ignored lines
Expand Down Expand Up @@ -189,6 +193,28 @@ def whitespace_around_operators(line, string_ranges):

return line

def features_standalone(self, line, _):
"""If the current file looks like a feature, then check a line for includes of feature templates
to ensure that they are children of the current feature"""
passed = True
diagnosis = ''
message = ''

if line.filename.startswith('features/'):
includes = RE_FEATURE_INCLUDE.finditer(line.text)
for include in includes:
this_file = line.filename
this_dir = dirname(this_file)
incl_file = include.group('name')
incl_dir = dirname(incl_file)
passed = passed and incl_dir.startswith(this_dir)
diagnosis = diagnose(*include.span('name'))

if not passed:
message = 'Feature template includes a template which is not a child of the feature'

return (passed, diagnosis, message)


def inside_string(i, j, string_ranges):
"""Returns true if the range described by i and j is contained within a range in the list string_ranges"""
Expand Down Expand Up @@ -496,6 +522,8 @@ def main():
help='Always exit cleanly even if problems are found')
parser.add_argument('--ignore-components', type=str,
help='List of component to ignore when checking included components')
parser.add_argument('--features_standalone', action='store_true',
help='Check that features don\'t include other features')
group_output = parser.add_mutually_exclusive_group()
group_output.add_argument('--debug', action='store_true', help='Enable debug output')
group_output.add_argument('--ide', action='store_true', help='Output machine-readable results for use by IDEs')
Expand All @@ -518,6 +546,10 @@ def main():
if args.ignore_components:
ignore_components = args.ignore_components.split(',')

# This check is based on site-specific policy, so it is optional
if not args.features_standalone:
del LineChecks.features_standalone

for path in args.paths:
for filename in glob(path):
file_problem_lines, file_problem_count = lint_file(filename, args.allow_mvn_templates, ignore_components)
Expand Down
13 changes: 13 additions & 0 deletions panc/src/main/scripts/panlint/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,19 @@ def test_print_report(self):
panlint.print_report(test_line)
self.assertEqual(mock_stdout.getvalue(), expected_output)

def test_feature_child_inclusion(self):
lc = panlint.LineChecks()
passed, _, message = lc.feature_child_inclusion(
panlint.Line('features/test/foo/config.pan', 3, "include 'features/test/foo/service/users';"),
[],
);
self.assertTrue(passed);
passed, _, message = lc.feature_child_inclusion(
panlint.Line('features/test/foo/config.pan', 3, "include 'features/test/bar/webserver';"),
[],
);
self.assertFalse(passed);


if __name__ == '__main__':
unittest.main()

0 comments on commit 371c16c

Please sign in to comment.