From 8c5461945e824bd37afc466041790b4f5c2c75bc Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Fri, 23 Jan 2026 08:30:23 +0100 Subject: [PATCH 01/10] Fix some violations found by running JuliaCheck on itself In particular, solved violations for: - `underscore-prefix-for-private-functions`. For the methods on Check subtypes, we solved it my making them explicitly extend the methods in Analysis. - `multiline-comments-for-many-lines` - `module-end-comment` --- checks/AvoidContainersWithAbstractTypes.jl | 36 ++++++++++--------- checks/AvoidCreatingEmptyArraysAndVectors.jl | 12 +++---- ...WhitespaceBetweenOpenAndCloseCharacters.jl | 12 ++++--- checks/AvoidGlobalVariables.jl | 8 ++--- checks/AvoidHardCodedNumbers.jl | 20 +++++------ checks/ConsistentLineEndings.jl | 8 ++--- checks/DoNotChangeGeneratedIndices.jl | 24 ++++++------- checks/DoNotCommentOutCode.jl | 8 ++--- checks/DoNotNestMultilineComments.jl | 16 ++++----- checks/DoNotSetVariablesToInf.jl | 8 ++--- checks/DoNotSetVariablesToNan.jl | 10 +++--- checks/DocumentConstants.jl | 14 ++++---- ...ationMarkInFunctionIdentifierIfMutating.jl | 14 ++++---- checks/FunctionArgumentsLowerSnakeCase.jl | 20 +++++------ checks/FunctionIdentifiersInLowerSnakeCase.jl | 12 +++---- .../FunctionsMutateOnlyZeroOrOneArguments.jl | 12 +++---- ...ConstVariablesShouldHaveTypeAnnotations.jl | 12 +++---- checks/GlobalVariablesUpperSnakeCase.jl | 8 ++--- checks/ImplementUnionsAsConsts.jl | 12 +++---- checks/IndentationLevelsAreFourSpaces.jl | 8 ++--- checks/IndentationOfModules.jl | 8 ++--- checks/InfiniteWhileLoop.jl | 12 +++---- checks/LeadingAndTrailingDigits.jl | 12 +++---- checks/LocationOfGlobalVariables.jl | 14 ++++---- ...unctionsHaveATerminatingReturnStatement.jl | 12 +++---- checks/ModuleEndComment.jl | 18 +++++----- checks/ModuleExportLocation.jl | 20 +++++------ checks/ModuleImportLocation.jl | 14 ++++---- checks/ModuleIncludeLocation.jl | 16 ++++----- checks/ModuleNameCasing.jl | 8 ++--- checks/ModuleSingleImportLine.jl | 12 +++---- checks/MultilineCommentsForManyLines.jl | 8 ++--- checks/NestingOfConditionalStatements.jl | 16 ++++----- checks/NewlineAtFileEnd.jl | 8 ++--- checks/NoWhitespaceAroundTypeOperators.jl | 23 ++++++------ checks/OmitTrailingWhiteSpace.jl | 12 +++---- checks/OneExpressionPerLine.jl | 8 ++--- ...nstVariablesOverNonConstGlobalVariables.jl | 8 ++--- checks/PrefixOfAbstractTypeNames.jl | 14 ++++---- checks/ShortHandFunctionTooComplicated.jl | 16 ++++----- checks/SingleModuleFile.jl | 12 +++---- checks/SingleSpaceAfterCommasAndSemicolons.jl | 18 +++++----- checks/SpaceAroundBinaryInfixOperators.jl | 10 +++--- checks/StructMembersAreInLowerSnakeCase.jl | 12 +++---- checks/TooManyTypesInUnions.jl | 10 +++--- checks/TypeNamesUpperCamelCase.jl | 10 +++--- checks/UnderscorePrefixForPrivateFunctions.jl | 8 ++--- checks/UseAmericanEnglish.jl | 8 ++--- checks/UseEachindexToIterateIndices.jl | 8 ++--- checks/UseIsinfToCheckForInfinite.jl | 8 ++--- checks/UseIsmissingToCheckForMissingValues.jl | 8 ++--- checks/UseIsnanToCheckForNan.jl | 8 ++--- checks/UseIsnothingToCheckForNothingValues.jl | 8 ++--- checks/UseSpacesInsteadOfTabs.jl | 10 +++--- checks/VariablesHaveFixedTypes.jl | 12 +++---- checks/_common.jl | 1 - src/Analysis.jl | 8 ++--- src/CommentHelpers.jl | 2 +- src/SymbolTable.jl | 14 ++++---- src/SyntaxNodeHelpers.jl | 2 +- src/TypeHelpers.jl | 8 ++--- 61 files changed, 361 insertions(+), 357 deletions(-) diff --git a/checks/AvoidContainersWithAbstractTypes.jl b/checks/AvoidContainersWithAbstractTypes.jl index f5d4046..f29021e 100644 --- a/checks/AvoidContainersWithAbstractTypes.jl +++ b/checks/AvoidContainersWithAbstractTypes.jl @@ -38,20 +38,22 @@ const ABSTRACT_NUMBER_TYPES = Set([ ]) struct Check<:Analysis.Check end -id(::Check) = "avoid-containers-with-abstract-types" -severity(::Check) = 6 -synopsis(::Check) = "Avoid containers with abstract types." +Analysis.id(::Check) = "avoid-containers-with-abstract-types" +Analysis.severity(::Check) = 6 +Analysis.synopsis(::Check) = "Avoid containers with abstract types." -function init(this::Check, ctxt::AnalysisContext)::Nothing - register_syntaxnode_action(ctxt, is_container, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing + register_syntaxnode_action(ctxt, _is_container, n -> _check(this, ctxt, n)) return nothing end -# Structure of a typical node we want to check here: -# (= num_vector (ref Real 1.0 2 3)) -# We want to check the type right-hand side of the assignment. -# For some invocations, this is also wrapped inside a call (eg. list comprehensions) -function is_container(node::SyntaxNode)::Bool +#= +Structure of a typical node we want to check here: +(= num_vector (ref Real 1.0 2 3)) +We want to check the type right-hand side of the assignment. +For some invocations, this is also wrapped inside a call (eg. list comprehensions) +=# +function _is_container(node::SyntaxNode)::Bool if !is_assignment(node) || numchildren(node) < 2 return false end @@ -59,7 +61,7 @@ function is_container(node::SyntaxNode)::Bool return !is_leaf(rhs) && kind(rhs) in KSet"ref call curly" end -function check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing assignment_rhs = children(node)[2] id_type_node = _get_identifier_node_to_check(assignment_rhs) if !isnothing(id_type_node) @@ -76,11 +78,13 @@ function check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing return nothing end -# Curly braces notations get translated like this: -# - Array{Number}[] => (curly Array Number) -# - Array{Array}{Number}[] => (curly Array (curly Array Number)) -# As such, to find the type of multidimensional arrays, it's convenient to be able -# to walk down the tree until an identifier is found. +#= +Curly braces notations get translated like this: +- Array{Number}[] => (curly Array Number) +- Array{Array}{Number}[] => (curly Array (curly Array Number)) +As such, to find the type of multidimensional arrays, it's convenient to be able +to walk down the tree until an identifier is found. +=# function _get_identifier_node_to_check(node::SyntaxNode)::NullableNode while _search_further(node) node = _get_next_search_node(node) diff --git a/checks/AvoidCreatingEmptyArraysAndVectors.jl b/checks/AvoidCreatingEmptyArraysAndVectors.jl index d92ae92..21428fc 100644 --- a/checks/AvoidCreatingEmptyArraysAndVectors.jl +++ b/checks/AvoidCreatingEmptyArraysAndVectors.jl @@ -6,16 +6,16 @@ using ...SymbolTable: node_is_declaration_of_variable include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "avoid-creating-empty-arrays-and-vectors" -severity(::Check) = 8 -synopsis(::Check) = "Avoid resizing arrays after initialization." +Analysis.id(::Check) = "avoid-creating-empty-arrays-and-vectors" +Analysis.severity(::Check) = 8 +Analysis.synopsis(::Check) = "Avoid resizing arrays after initialization." -function init(this::Check, ctxt::AnalysisContext)::Nothing - register_syntaxnode_action(ctxt, is_assignment, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing + register_syntaxnode_action(ctxt, is_assignment, n -> _check(this, ctxt, n)) return nothing end -function check(this::Check, ctxt::AnalysisContext, assignment_node::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, assignment_node::SyntaxNode)::Nothing if ! node_is_declaration_of_variable(ctxt.symboltable, first(children(assignment_node))) return end diff --git a/checks/AvoidExtraneousWhitespaceBetweenOpenAndCloseCharacters.jl b/checks/AvoidExtraneousWhitespaceBetweenOpenAndCloseCharacters.jl index e6ebc81..764abc9 100644 --- a/checks/AvoidExtraneousWhitespaceBetweenOpenAndCloseCharacters.jl +++ b/checks/AvoidExtraneousWhitespaceBetweenOpenAndCloseCharacters.jl @@ -6,9 +6,11 @@ using ...Properties: is_toplevel include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "avoid-extraneous-whitespace-between-open-and-close-characters" -severity(::Check) = 7 -synopsis(::Check) = "Avoid extraneous whitespace inside parentheses, square brackets or braces." +Analysis.id(::Check) = "avoid-extraneous-whitespace-between-open-and-close-characters" +Analysis.severity(::Check) = 7 +function Analysis.synopsis(::Check)::String + return "Avoid extraneous whitespace inside parentheses, square brackets or braces." +end """ Syntax node types for which whitespace should be checked. @@ -100,7 +102,7 @@ function _check(this::Check, ctxt::AnalysisContext, sf::SourceFile)::Nothing location_msg = " before '$next_leaf_text'" else expected_spaces = 1 # Exactly one space between elements - location_msg = " between '$prev_leaf_text' and '$next_leaf_text'" + location_msg = " between '$prev_leaf_text' and '$next_leaf_text'" end if !isnothing(expected_spaces) && length(cur.range) != expected_spaces @@ -111,7 +113,7 @@ function _check(this::Check, ctxt::AnalysisContext, sf::SourceFile)::Nothing return nothing end -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_toplevel, root -> _check(this, ctxt, root.source)) return nothing end diff --git a/checks/AvoidGlobalVariables.jl b/checks/AvoidGlobalVariables.jl index 5243491..e5d6030 100644 --- a/checks/AvoidGlobalVariables.jl +++ b/checks/AvoidGlobalVariables.jl @@ -10,11 +10,11 @@ struct Check<:Analysis.Check already_reported::Set{SyntaxNode} Check() = new(Set{SyntaxNode}()) end -id(::Check) = "avoid-global-variables" -severity(::Check) = 3 -synopsis(::Check) = "Avoid global variables when possible" +Analysis.id(::Check) = "avoid-global-variables" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Avoid global variables when possible" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_global_decl, node -> begin id = find_lhs_of_kind(K"Identifier", node) if isnothing(id) diff --git a/checks/AvoidHardCodedNumbers.jl b/checks/AvoidHardCodedNumbers.jl index 154e50d..ca7543f 100644 --- a/checks/AvoidHardCodedNumbers.jl +++ b/checks/AvoidHardCodedNumbers.jl @@ -11,18 +11,18 @@ struct Check<:Analysis.Check # probably use a tolerance to compare them. Check() = new(Set{Number}()) end -id(::Check) = "avoid-hard-coded-numbers" -severity(::Check) = 3 -synopsis(::Check) = "Avoid hard-coded numbers" +Analysis.id(::Check) = "avoid-hard-coded-numbers" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Avoid hard-coded numbers" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_literal_number, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_literal_number, n -> _check(this, ctxt, n)) end # Also FIXME: should I use all the 64 bits versions of the types? -function check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) @assert is_literal_number(node) "Expected a node with a literal number, got $(kind(node))" - if !is_const_declaration(node) && !in_array_assignment(node) && is_magic_number(node) + if !_is_const_declaration(node) && !_in_array_assignment(node) && _is_magic_number(node) n = get_number(node) if n ∈ this.seen_before report_violation(ctxt, this, node, "Hard-coded number '$n' should be a const variable.") @@ -39,7 +39,7 @@ Check if the literal number is part of a constant declaration. To that end, we climb up the tree until we find a constant declaration, or the root. """ -function is_const_declaration(node::SyntaxNode)::Bool +function _is_const_declaration(node::SyntaxNode)::Bool x = node while !(isnothing(x) || is_constant(x)) x = x.parent @@ -47,7 +47,7 @@ function is_const_declaration(node::SyntaxNode)::Bool return !isnothing(x) end -function in_array_assignment(node::SyntaxNode)::Bool +function _in_array_assignment(node::SyntaxNode)::Bool p = node.parent return !isnothing(p) && kind(p) == K"vect" end @@ -75,7 +75,7 @@ const KNOWN_FLOATS = Set{Float64}([0.1, 0.01, 0.001, 0.0001, 0.5]) ∪ Check if the given literal is a magic number, i.e., it is not a "usual number", i.e., one usually found in initializations. """ -function is_magic_number(node::SyntaxNode)::Bool +function _is_magic_number(node::SyntaxNode)::Bool n = get_number(node) return !isnothing(n) && ( kind(node) == K"Float" ? n ∉ KNOWN_FLOATS : n ∉ KNOWN_INTS diff --git a/checks/ConsistentLineEndings.jl b/checks/ConsistentLineEndings.jl index 450cd56..8fcd2aa 100644 --- a/checks/ConsistentLineEndings.jl +++ b/checks/ConsistentLineEndings.jl @@ -6,12 +6,12 @@ using ...Properties: is_toplevel include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "consistent-line-endings" -severity(::Check) = 7 -synopsis(::Check) = "Make sure that the line endings are consistent within a file" +Analysis.id(::Check) = "consistent-line-endings" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Make sure that the line endings are consistent within a file" -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_toplevel, n -> _check(this, ctxt, n)) return nothing end diff --git a/checks/DoNotChangeGeneratedIndices.jl b/checks/DoNotChangeGeneratedIndices.jl index 436b87d..fb0747d 100644 --- a/checks/DoNotChangeGeneratedIndices.jl +++ b/checks/DoNotChangeGeneratedIndices.jl @@ -4,22 +4,22 @@ using ...Properties: first_child, get_assignee, get_iteration_parts, is_assignment, is_flow_cntrl, is_range include("_common.jl") - + struct Check<:Analysis.Check end -id(::Check) = "do-not-change-generated-indices" -severity(::Check) = 5 -synopsis(::Check) = "Do not change generated indices" +Analysis.id(::Check) = "do-not-change-generated-indices" +Analysis.severity(::Check) = 5 +Analysis.synopsis(::Check) = "Do not change generated indices" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, n -> kind(n) == K"for", n -> checkForLoop(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, n -> kind(n) == K"for", n -> _check_for_loop(this, ctxt, n)) end -function checkForLoop(this::Check, ctxt::AnalysisContext, for_loop::SyntaxNode) +function _check_for_loop(this::Check, ctxt::AnalysisContext, for_loop::SyntaxNode) loop_var, iter_expr = get_iteration_parts(for_loop) if isnothing(loop_var) || isnothing(iter_expr) return nothing end - var_name = loop_var_to_string(loop_var) + var_name = _loop_var_to_string(loop_var) if is_range(iter_expr) || ( kind(iter_expr) == K"call" && kind(first_child(iter_expr)) == K"Identifier" && @@ -29,11 +29,11 @@ function checkForLoop(this::Check, ctxt::AnalysisContext, for_loop::SyntaxNode) @assert numchildren(for_loop) == 2 && kind(children(for_loop)[2]) == K"block" "An empty loop or what? $for_loop" body = children(for_loop)[2] - frisk_for_modification(this, ctxt, body, var_name) + _frisk_for_modification(this, ctxt, body, var_name) end end -function loop_var_to_string(var::SyntaxNode) +function _loop_var_to_string(var::SyntaxNode) x = var if kind(x) == K"tuple" x = first_child(x) end if kind(x) == K"Identifier" return string(x) end @@ -41,7 +41,7 @@ function loop_var_to_string(var::SyntaxNode) return "" end -function frisk_for_modification(this::Check, ctxt::AnalysisContext, body::SyntaxNode, var_name::String)::Nothing +function _frisk_for_modification(this::Check, ctxt::AnalysisContext, body::SyntaxNode, var_name::String)::Nothing for expr in children(body) if is_assignment(expr) lhs_node, lhs_str = get_assignee(expr) @@ -52,7 +52,7 @@ function frisk_for_modification(this::Check, ctxt::AnalysisContext, body::Syntax elseif is_flow_cntrl(expr) next_victim = findfirst(x -> kind(x) == K"block", children(expr)) if ! isnothing(next_victim) - frisk_for_modification(this, ctxt, children(expr)[next_victim], var_name) + _frisk_for_modification(this, ctxt, children(expr)[next_victim], var_name) end end end diff --git a/checks/DoNotCommentOutCode.jl b/checks/DoNotCommentOutCode.jl index 5b7f900..0822cae 100644 --- a/checks/DoNotCommentOutCode.jl +++ b/checks/DoNotCommentOutCode.jl @@ -18,12 +18,12 @@ const KEYWORDS = ["baremodule", "begin", "break", "const", "continue", "do", "ex "type", "var", "(", ")"] struct Check<:Analysis.Check end -id(::Check) = "do-not-comment-out-code" -severity(::Check) = 9 -synopsis(::Check) = "Do not comment out code." +Analysis.id(::Check) = "do-not-comment-out-code" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "Do not comment out code." -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, contains_comments, n -> _check(this, ctxt, n)) return nothing end diff --git a/checks/DoNotNestMultilineComments.jl b/checks/DoNotNestMultilineComments.jl index 7c9f1c5..e9c4cd4 100644 --- a/checks/DoNotNestMultilineComments.jl +++ b/checks/DoNotNestMultilineComments.jl @@ -6,26 +6,26 @@ using ...SyntaxNodeHelpers include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "do-not-nest-multiline-comments" -severity(::Check) = 9 -synopsis(::Check) = "Don't nest multiline comments" +Analysis.id(::Check) = "do-not-nest-multiline-comments" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "Don't nest multiline comments" const ML_COMMENT = "#=" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_toplevel, node -> begin code = node.source.code comments = filter(gl -> kind(gl) == K"Comment", ctxt.greenleaves) for comment in comments text = sourcetext(comment) if startswith(text, ML_COMMENT) # We are only interested in multiline comments - # Search for next comment inside comment + # Search for next comment inside comment found::Union{UnitRange{Int}, Nothing} = findnext(ML_COMMENT, text, length(ML_COMMENT)) if !isnothing(found) found = (comment.range.start-1) .+ found - report_violation(ctxt, this, - source_location(node.source, found.start), - found, + report_violation(ctxt, this, + source_location(node.source, found.start), + found, synopsis(this) ) end diff --git a/checks/DoNotSetVariablesToInf.jl b/checks/DoNotSetVariablesToInf.jl index d86dce8..7354ef9 100644 --- a/checks/DoNotSetVariablesToInf.jl +++ b/checks/DoNotSetVariablesToInf.jl @@ -5,11 +5,11 @@ using ...SyntaxNodeHelpers include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "do-not-set-variables-to-inf" -severity(::Check) = 3 -synopsis(::Check) = "Do not set variables to Inf, Inf16, Inf32 or Inf64" +Analysis.id(::Check) = "do-not-set-variables-to-inf" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Do not set variables to Inf, Inf16, Inf32 or Inf64" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) == K"=", node -> begin if numchildren(node) != 2 @debug "Assignment with $(numchildren(node)) children instead of 2." diff --git a/checks/DoNotSetVariablesToNan.jl b/checks/DoNotSetVariablesToNan.jl index 3a320fc..2de685e 100644 --- a/checks/DoNotSetVariablesToNan.jl +++ b/checks/DoNotSetVariablesToNan.jl @@ -5,11 +5,11 @@ using ...SyntaxNodeHelpers include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "do-not-set-variables-to-nan" -severity(::Check) = 3 -synopsis(::Check) = "Do not set variables to NaN, NaN16, NaN32 or NaN64" +Analysis.id(::Check) = "do-not-set-variables-to-nan" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Do not set variables to NaN, NaN16, NaN32 or NaN64" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) == K"=", node -> begin if numchildren(node) != 2 @debug "Assignment with $(numchildren(node)) children instead of 2." @@ -20,7 +20,7 @@ function init(this::Check, ctxt::AnalysisContext) if extract_special_value(rhs) ∈ SyntaxNodeHelpers.NAN_VALUES report_violation(ctxt, this, rhs, synopsis(this)) end - + end) end diff --git a/checks/DocumentConstants.jl b/checks/DocumentConstants.jl index ed5a51a..c49a30a 100644 --- a/checks/DocumentConstants.jl +++ b/checks/DocumentConstants.jl @@ -5,15 +5,15 @@ using ...Properties: find_lhs_of_kind, haschildren, is_constant include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "document-constants" -severity(::Check) = 7 -synopsis(::Check) = "Constants must have a docstring" +Analysis.id(::Check) = "document-constants" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Constants must have a docstring" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_constant, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_constant, n -> _check(this, ctxt, n)) end -function check(this::Check, ctxt::AnalysisContext, const_node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, const_node::SyntaxNode) if kind(const_node) == K"global" if haschildren(const_node) const_node = children(const_node)[1] @@ -39,4 +39,4 @@ function check(this::Check, ctxt::AnalysisContext, const_node::SyntaxNode) end end -end +end # module DocumentConstants diff --git a/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl b/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl index 2534968..1d374b7 100644 --- a/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl +++ b/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl @@ -5,19 +5,19 @@ using ...MutatingFunctionsHelpers: get_mutated_variables_in_scope using ...Properties: get_func_body, get_func_name, get_string_fn_args, is_function include("_common.jl") -struct Check <: Analysis.Check end +struct Check<:Analysis.Check end -id(::Check) = "exclamation-mark-in-function-identifier-if-mutating" -severity(::Check) = 4 -synopsis(::Check) = "Only functions postfixed with an exclamation mark can mutate an argument." +Analysis.id(::Check) = "exclamation-mark-in-function-identifier-if-mutating" +Analysis.severity(::Check) = 4 +Analysis.synopsis(::Check) = "Only functions postfixed with an exclamation mark can mutate an argument." -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, _is_nonmutating_fn, n -> check_function(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, _is_nonmutating_fn, n -> _check_function(this, ctxt, n)) end _is_nonmutating_fn(n::SyntaxNode)::Bool = is_function(n) && !endswith(string(get_func_name(n)), "!") -function check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode) +function _check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode) func_arg_strings = get_string_fn_args(function_node) all_mutated_variables = get_mutated_variables_in_scope(ctxt, get_func_body(function_node)) for func_arg in func_arg_strings diff --git a/checks/FunctionArgumentsLowerSnakeCase.jl b/checks/FunctionArgumentsLowerSnakeCase.jl index b93b602..9c0b556 100644 --- a/checks/FunctionArgumentsLowerSnakeCase.jl +++ b/checks/FunctionArgumentsLowerSnakeCase.jl @@ -5,11 +5,11 @@ using ...Properties: find_lhs_of_kind, is_lower_snake, get_func_name, get_func_a include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "function-arguments-lower-snake-case" -severity(::Check) = 7 -synopsis(::Check) = "Function arguments must be written in \"lower_snake_case\"" +Analysis.id(::Check) = "function-arguments-lower-snake-case" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Function arguments must be written in \"lower_snake_case\"" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) == K"function", node -> begin fname = get_func_name(node) fname_str = string(fname) @@ -21,20 +21,20 @@ function init(this::Check, ctxt::AnalysisContext) end # The last argument in the list is itself a list, of named arguments. for arg in children(arg) - checkArgument(this, ctxt, fname_str, arg) + _check_argument(this, ctxt, fname_str, arg) end else - checkArgument(this, ctxt, fname_str, arg) + _check_argument(this, ctxt, fname_str, arg) end end end) end -function checkArgument(this::Check, ctxt::AnalysisContext, f_name::AbstractString, f_arg::SyntaxNode) +function _check_argument(this::Check, ctxt::AnalysisContext, f_name::AbstractString, f_arg::SyntaxNode) if kind(f_arg) == K"::" f_arg = numchildren(f_arg) == 1 ? nothing : children(f_arg)[1] end - if f_arg !== nothing + if ! isnothing(f_arg) f_arg = find_lhs_of_kind(K"Identifier", f_arg) end if isnothing(f_arg) @@ -44,11 +44,11 @@ function checkArgument(this::Check, ctxt::AnalysisContext, f_name::AbstractStrin end arg_name = string(f_arg) if ! is_lower_snake(arg_name) - report_violation(ctxt, this, f_arg, + report_violation(ctxt, this, f_arg, "Argument '$arg_name' of function '$f_name' must be written in \"lower_snake_case\"." ) end end -end # module FunctionArgumentsInLowerSnakeCase +end # module FunctionArgumentsLowerSnakeCase diff --git a/checks/FunctionIdentifiersInLowerSnakeCase.jl b/checks/FunctionIdentifiersInLowerSnakeCase.jl index 23a366b..d7d7fd0 100644 --- a/checks/FunctionIdentifiersInLowerSnakeCase.jl +++ b/checks/FunctionIdentifiersInLowerSnakeCase.jl @@ -5,22 +5,22 @@ using ...Properties: inside, is_lower_snake, is_struct, get_func_name include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "function-identifiers-in-lower-snake-case" -severity(::Check) = 8 -synopsis(::Check) = "Function name should be written in \"lower_snake_case\"" +Analysis.id(::Check) = "function-identifiers-in-lower-snake-case" +Analysis.severity(::Check) = 8 +Analysis.synopsis(::Check) = "Function name should be written in \"lower_snake_case\"" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) == K"function", node -> begin fname = get_func_name(node) if kind(fname.parent) == K"." return #RM-37316: do not trigger on extension of a function defined in another module end - checkFunctionName(this, ctxt, fname) + _check_function_name(this, ctxt, fname) end) end -function checkFunctionName(this::Check, ctxt::AnalysisContext, func_name::SyntaxNode) +function _check_function_name(this::Check, ctxt::AnalysisContext, func_name::SyntaxNode) @assert kind(func_name) == K"Identifier" "Expected an [Identifier] node, got [$(kind(func_name))]" if inside(func_name, is_struct) # Inner constructors (functions inside a type definition) must match the diff --git a/checks/FunctionsMutateOnlyZeroOrOneArguments.jl b/checks/FunctionsMutateOnlyZeroOrOneArguments.jl index 4c91202..200f46a 100644 --- a/checks/FunctionsMutateOnlyZeroOrOneArguments.jl +++ b/checks/FunctionsMutateOnlyZeroOrOneArguments.jl @@ -7,15 +7,15 @@ using ...Properties: get_flattened_fn_arg_nodes, get_func_body, get_string_arg, include("_common.jl") struct Check <: Analysis.Check end -id(::Check) = "functions-mutate-only-zero-or-one-arguments" -severity(::Check) = 3 -synopsis(::Check) = "Functions should change only one or zero argument(s)." +Analysis.id(::Check) = "functions-mutate-only-zero-or-one-arguments" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Functions should change only one or zero argument(s)." -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_function, n -> check_function(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_function, n -> _check_function(this, ctxt, n)) end -function check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode) +function _check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode) func_arg_nodes = get_flattened_fn_arg_nodes(function_node) all_mutated_variables = get_mutated_variables_in_scope(ctxt, get_func_body(function_node)) for func_arg in func_arg_nodes[2:end] diff --git a/checks/GlobalNonConstVariablesShouldHaveTypeAnnotations.jl b/checks/GlobalNonConstVariablesShouldHaveTypeAnnotations.jl index 584bcf1..cb1caa6 100644 --- a/checks/GlobalNonConstVariablesShouldHaveTypeAnnotations.jl +++ b/checks/GlobalNonConstVariablesShouldHaveTypeAnnotations.jl @@ -5,18 +5,18 @@ using ...Properties: first_child, is_constant, is_global_decl, haschildren, is_m include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "global-non-const-variables-should-have-type-annotations" -severity(::Check) = 6 -synopsis(::Check) = "Global non-const variables should have type annotations" +Analysis.id(::Check) = "global-non-const-variables-should-have-type-annotations" +Analysis.severity(::Check) = 6 +Analysis.synopsis(::Check) = "Global non-const variables should have type annotations" -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> is_global_decl(n) && !is_constant(n) && !isnothing(n.parent) && is_mod_toplevel(n.parent), node -> begin - check(this, ctxt, node) + _check(this, ctxt, node) end) return nothing end -function check(this::Check, ctxt::AnalysisContext, glob_var::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, glob_var::SyntaxNode)::Nothing # This must not be a [const], so it must be [global]. @assert is_global_decl(glob_var) "Expected a global declaration, got [$(kind(glob_var))]." @assert !is_constant(glob_var) "Run this check on non-const global declarations only!" diff --git a/checks/GlobalVariablesUpperSnakeCase.jl b/checks/GlobalVariablesUpperSnakeCase.jl index 55fb4ee..8c1ae95 100644 --- a/checks/GlobalVariablesUpperSnakeCase.jl +++ b/checks/GlobalVariablesUpperSnakeCase.jl @@ -7,11 +7,11 @@ using ...SyntaxNodeHelpers: get_all_assignees include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "global-variables-upper-snake-case" -severity(::Check) = 3 -synopsis(::Check) = "Casing of globals" +Analysis.id(::Check) = "global-variables-upper-snake-case" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Casing of globals" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> is_assignment(n) && !is_field_assignment(n), n -> begin ids = get_all_assignees(n) for id in ids diff --git a/checks/ImplementUnionsAsConsts.jl b/checks/ImplementUnionsAsConsts.jl index 3ee9599..f7c266c 100644 --- a/checks/ImplementUnionsAsConsts.jl +++ b/checks/ImplementUnionsAsConsts.jl @@ -5,17 +5,17 @@ using ...Properties: is_assignment, is_constant, is_union_decl include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "implement-unions-as-consts" -severity(::Check) = 3 -synopsis(::Check) = "Implement Unions as const" +Analysis.id(::Check) = "implement-unions-as-consts" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Implement Unions as const" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_union_decl, node -> begin - check_union(this, ctxt, node) + _check_union(this, ctxt, node) end) end -function check_union(this::Check, ctxt::AnalysisContext, union::SyntaxNode)::Nothing +function _check_union(this::Check, ctxt::AnalysisContext, union::SyntaxNode)::Nothing @assert is_union_decl(union) "Expected a Union declaration, got $(kind(union))" if is_assignment(union.parent) && is_constant(union.parent.parent) # This seems to be a Union type declaration diff --git a/checks/IndentationLevelsAreFourSpaces.jl b/checks/IndentationLevelsAreFourSpaces.jl index 827042d..54ecc9f 100644 --- a/checks/IndentationLevelsAreFourSpaces.jl +++ b/checks/IndentationLevelsAreFourSpaces.jl @@ -6,11 +6,11 @@ using ...Properties: is_toplevel using ...SyntaxNodeHelpers struct Check<:Analysis.Check end -id(::Check) = "indentation-levels-are-four-spaces" -severity(::Check) = 7 -synopsis(::Check) = "Indentation should be a multiple of four spaces" +Analysis.id(::Check) = "indentation-levels-are-four-spaces" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Indentation should be a multiple of four spaces" -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_toplevel, n -> begin for gl in ctxt.greenleaves # We will inspect nodes of kind [NewlineWs] containing indentation spaces diff --git a/checks/IndentationOfModules.jl b/checks/IndentationOfModules.jl index 4d690b4..94f3a8d 100644 --- a/checks/IndentationOfModules.jl +++ b/checks/IndentationOfModules.jl @@ -8,11 +8,11 @@ using ...SyntaxNodeHelpers: ancestors using ...WhitespaceHelpers: normalized_green_child_range struct Check<:Analysis.Check end -id(::Check) = "indentation-of-modules" -severity(::Check) = 7 -synopsis(::Check) = "Do not indent top level module body, do indent submodules" +Analysis.id(::Check) = "indentation-of-modules" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Do not indent top level module body, do indent submodules" -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) return nothing end diff --git a/checks/InfiniteWhileLoop.jl b/checks/InfiniteWhileLoop.jl index 982048e..55e480c 100644 --- a/checks/InfiniteWhileLoop.jl +++ b/checks/InfiniteWhileLoop.jl @@ -3,15 +3,15 @@ module InfiniteWhileLoop include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "infinite-while-loop" -severity(::Check) = 5 -synopsis(::Check) = "Do not use while true" +Analysis.id(::Check) = "infinite-while-loop" +Analysis.severity(::Check) = 5 +Analysis.synopsis(::Check) = "Do not use while true" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, n -> kind(n) == K"while", n -> checkWhileNode(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, n -> kind(n) == K"while", n -> _check_while_node(this, ctxt, n)) end -function checkWhileNode(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing +function _check_while_node(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing @assert kind(node) == K"while" "Expected a [while], got $(kind(node))" @assert numchildren(node) > 0 "A [while] without children! Is this an incomplete tree, from code under edition?" condition = children(node)[1] diff --git a/checks/LeadingAndTrailingDigits.jl b/checks/LeadingAndTrailingDigits.jl index e36406f..d343636 100644 --- a/checks/LeadingAndTrailingDigits.jl +++ b/checks/LeadingAndTrailingDigits.jl @@ -5,15 +5,15 @@ include("_common.jl") using JuliaSyntax: sourcetext struct Check<:Analysis.Check end -id(::Check) = "leading-and-trailing-digits" -severity(::Check) = 3 -synopsis(::Check) = "Floating-point numbers should always have one digit before the decimal point and at least one after" +Analysis.id(::Check) = "leading-and-trailing-digits" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Floating-point numbers should always have one digit before the decimal point and at least one after" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, n -> kind(n) == K"Float", n -> checkFloatNode(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, n -> kind(n) == K"Float", n -> _check_float_node(this, ctxt, n)) end -function checkFloatNode(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing +function _check_float_node(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing text = sourcetext(node) index = findfirst('.', text) diff --git a/checks/LocationOfGlobalVariables.jl b/checks/LocationOfGlobalVariables.jl index 6c0ab05..8c087b2 100644 --- a/checks/LocationOfGlobalVariables.jl +++ b/checks/LocationOfGlobalVariables.jl @@ -5,15 +5,15 @@ include("_common.jl") using ...Properties: haschildren, is_export, is_global_decl, is_import, is_mod_toplevel struct Check<:Analysis.Check end -id(::Check) = "location-of-global-variables" -severity(::Check) = 7 -synopsis(::Check) = "Global variables should be placed at the top of a module or file" +Analysis.id(::Check) = "location-of-global-variables" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Global variables should be placed at the top of a module or file" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_global_decl, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_global_decl, n -> _check(this, ctxt, n)) end -function check(this::Check, ctxt::AnalysisContext, glob_decl::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, glob_decl::SyntaxNode)::Nothing @assert is_global_decl(glob_decl) "Expected a global declaration node, got $(kind(glob_decl))" toplevel = glob_decl.parent if !is_mod_toplevel(toplevel) @@ -36,4 +36,4 @@ function check(this::Check, ctxt::AnalysisContext, glob_decl::SyntaxNode)::Nothi return nothing end -end +end # LocationOfGlobalVariables diff --git a/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl b/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl index 331fc04..1357207 100644 --- a/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl +++ b/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl @@ -6,20 +6,20 @@ using ...Properties: inside, is_struct, get_func_name, get_func_body, haschildre using ...WhitespaceHelpers: normalized_green_child_range struct Check<:Analysis.Check end -id(::Check) = "long-form-functions-have-a-terminating-return-statement" -severity(::Check) = 3 -synopsis(::Check) = "Long form functions should end with an explicit return statement" +Analysis.id(::Check) = "long-form-functions-have-a-terminating-return-statement" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Long form functions should end with an explicit return statement" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) == K"function", node -> begin body = get_func_body(node) if body !== nothing - checkFuncBody(this, ctxt, body) + _check(this, ctxt, body) end end) end -function checkFuncBody(this::Check, ctxt::AnalysisContext, func_body::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, func_body::SyntaxNode)::Nothing @assert kind(func_body.parent) == K"function" "Expected the body of a [function], got $(kind(func_body))" fname = get_func_name(func_body.parent) if isnothing(fname) fname = "" end diff --git a/checks/ModuleEndComment.jl b/checks/ModuleEndComment.jl index 4f10b17..854f730 100644 --- a/checks/ModuleEndComment.jl +++ b/checks/ModuleEndComment.jl @@ -6,15 +6,15 @@ using JuliaSyntax: last_byte using ...Properties: is_module, is_toplevel, get_module_name struct Check<:Analysis.Check end -id(::Check) = "module-end-comment" -severity(::Check) = 9 -synopsis(::Check) = "The end statement of a module should have a comment with the module name" +Analysis.id(::Check) = "module-end-comment" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "The end statement of a module should have a comment with the module name" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_module, n -> checkModule2(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) end -function checkModule2(this::Check, ctxt::AnalysisContext, mod::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, mod::SyntaxNode)::Nothing code = mod.source.code mod_end = last_byte(mod) eol = something(findnext('\n', code, mod_end), length(code)) @@ -22,11 +22,11 @@ function checkModule2(this::Check, ctxt::AnalysisContext, mod::SyntaxNode)::Noth if comment_start >= eol filepos = source_location(mod.source, mod_end) report_violation(ctxt, this, filepos, range(mod_end-2, length=3), "Missing end module comment") - else + else comment_range = comment_start:eol comment = code[comment_range] (_, mod_name_str) = get_module_name(mod) - if !matches_module_name(mod_name_str, comment) + if !_matches_module_name(mod_name_str, comment) filepos = source_location(mod.source, mod_end) report_violation(ctxt, this, filepos, comment_range, synopsis(this)) end @@ -34,7 +34,7 @@ function checkModule2(this::Check, ctxt::AnalysisContext, mod::SyntaxNode)::Noth return nothing end -function matches_module_name(mod_name::AbstractString, comment::AbstractString) +function _matches_module_name(mod_name::AbstractString, comment::AbstractString) return occursin(Regex("(module[ ]+)?" * mod_name), comment) end diff --git a/checks/ModuleExportLocation.jl b/checks/ModuleExportLocation.jl index 73f7d8e..74302cc 100644 --- a/checks/ModuleExportLocation.jl +++ b/checks/ModuleExportLocation.jl @@ -5,27 +5,27 @@ include("_common.jl") using ...Properties: is_export, is_import, is_module struct Check<:Analysis.Check end -id(::Check) = "module-export-location" -severity(::Check) = 9 -synopsis(::Check) = "Exports should be implemented after the include instructions" +Analysis.id(::Check) = "module-export-location" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "Exports should be implemented after the include instructions" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_module, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) end -no_ex_imports(node::SyntaxNode) = ! (is_import(node) || is_export(node)) +_no_ex_imports(node::SyntaxNode) = ! (is_import(node) || is_export(node)) -function check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing @assert kind(modjule) == K"module" "Expected a [module] node, got [$(kind(modjule))]." @assert numchildren(modjule) == 2 "This module has a weird shape: "* string(modjule) @assert kind(children(modjule)[2]) == K"block" "The second child of a [module] node is not a [block]!" mod_body = children(children(modjule)[2]) last_export = findlast(is_export, mod_body) - if last_export === nothing return nothing end + if isnothing(last_export) return nothing end - code_begin = findfirst(no_ex_imports, mod_body) - if code_begin === nothing + code_begin = findfirst(_no_ex_imports, mod_body) + if isnothing(code_begin) # Nothing to check that is not yet covered by other rules. return nothing end diff --git a/checks/ModuleImportLocation.jl b/checks/ModuleImportLocation.jl index e6547c0..0ff9fcf 100644 --- a/checks/ModuleImportLocation.jl +++ b/checks/ModuleImportLocation.jl @@ -5,24 +5,24 @@ include("_common.jl") using ...Properties: is_import, is_include, is_module struct Check<:Analysis.Check end -id(::Check) = "module-import-location" -severity(::Check) = 9 -synopsis(::Check) = "Packages should be imported after the module keyword." +Analysis.id(::Check) = "module-import-location" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "Packages should be imported after the module keyword." const USER_MSG = "Move imports to the top of the module, before any actual code" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_module, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) end -function check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing @assert kind(modjule) == K"module" "Expected a [module] node, got [$(kind(node))]." @assert numchildren(modjule) == 2 "This module has a weird shape: "* string(modjule) @assert kind(children(modjule)[2]) == K"block" "The second child of a [module] node is not a [block]!" mod_body = children(children(modjule)[2]) code_starts_here = findfirst(!is_import, mod_body) - if code_starts_here !== nothing + if ! isnothing(code_starts_here) for node in mod_body[code_starts_here:end] if is_import(node) && !is_include(node) # We can skip include's because they are followed by an import diff --git a/checks/ModuleIncludeLocation.jl b/checks/ModuleIncludeLocation.jl index 8b29366..7b7a2c3 100644 --- a/checks/ModuleIncludeLocation.jl +++ b/checks/ModuleIncludeLocation.jl @@ -5,27 +5,27 @@ using ...Properties: get_imported_pkg, is_import, is_include, is_module include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "module-include-location" -severity(::Check) = 9 -synopsis(::Check) = "The list of included files should be after the list of imported packages" +Analysis.id(::Check) = "module-include-location" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "The list of included files should be after the list of imported packages" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_module, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) end -function check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing @assert kind(modjule) == K"module" "Expected a [module] node, got [$(kind(modjule))]." @assert numchildren(modjule) == 2 "This module has a weird shape: "* string(modjule) @assert kind(children(modjule)[2]) == K"block" "The second child of a [module] node is not a [block]!" mod_body = children(children(modjule)[2]) code_beginning = findfirst(!is_import, mod_body) - if code_beginning === nothing + if isnothing(code_beginning) # No code, only imports. It usually happens in packages "entry" files. code_beginning = length(mod_body) + 1 end includes_start = findfirst(is_include, mod_body[1:code_beginning-1]) - if includes_start !== nothing + if ! isnothing(includes_start) for (i, node) in enumerate(mod_body[includes_start+1 : code_beginning-1]) if !is_include(node) # It must be an [import] or [using] diff --git a/checks/ModuleNameCasing.jl b/checks/ModuleNameCasing.jl index 57b41b9..80b0a7c 100644 --- a/checks/ModuleNameCasing.jl +++ b/checks/ModuleNameCasing.jl @@ -4,11 +4,11 @@ include("_common.jl") using ...Properties: get_module_name, is_upper_camel_case struct Check<:Analysis.Check end -id(::Check) = "module-name-casing" -severity(::Check) = 5 -synopsis(::Check) = "Package names and module names should be written in UpperCamelCase" +Analysis.id(::Check) = "module-name-casing" +Analysis.severity(::Check) = 5 +Analysis.synopsis(::Check) = "Package names and module names should be written in UpperCamelCase" -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"module", node -> begin (mod_id_node, module_name) = get_module_name(node) if ! is_upper_camel_case(module_name) diff --git a/checks/ModuleSingleImportLine.jl b/checks/ModuleSingleImportLine.jl index 17e8d13..eb5af67 100644 --- a/checks/ModuleSingleImportLine.jl +++ b/checks/ModuleSingleImportLine.jl @@ -4,15 +4,15 @@ include("_common.jl") using ...Properties: get_imported_pkg, is_import, is_include, is_module struct Check<:Analysis.Check end -id(::Check) = "module-single-import-line" -severity(::Check) = 9 -synopsis(::Check) = "The list of packages should be in alphabetical order" +Analysis.id(::Check) = "module-single-import-line" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "The list of packages should be in alphabetical order" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_module, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) end -function check(this::Check, ctxt::AnalysisContext, module_node::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, module_node::SyntaxNode)::Nothing @assert kind(module_node) == K"module" "Expected a [module] node, got [$(kind(module_node))]." @assert numchildren(module_node) == 2 "This module has a weird shape: "* string(module_node) @assert kind(children(module_node)[2]) == K"block" "The second child of a [module] node is not a [block]!" diff --git a/checks/MultilineCommentsForManyLines.jl b/checks/MultilineCommentsForManyLines.jl index 319b375..3232677 100644 --- a/checks/MultilineCommentsForManyLines.jl +++ b/checks/MultilineCommentsForManyLines.jl @@ -5,11 +5,11 @@ using ...CommentHelpers: CommentBlock, get_comment_blocks, get_range, contains_c include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "multiline-comments-for-many-lines" -severity(::Check) = 9 -synopsis(::Check) = "Use multiline comments for large blocks." +Analysis.id(::Check) = "multiline-comments-for-many-lines" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "Use multiline comments for large blocks." -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, contains_comments, n -> _check(this, ctxt, n)) return nothing end diff --git a/checks/NestingOfConditionalStatements.jl b/checks/NestingOfConditionalStatements.jl index fcb6bee..b3183ee 100644 --- a/checks/NestingOfConditionalStatements.jl +++ b/checks/NestingOfConditionalStatements.jl @@ -5,28 +5,28 @@ include("_common.jl") using ...Properties: is_flow_cntrl, is_stop_point struct Check<:Analysis.Check end -id(::Check) = "nesting-of-conditional-statements" -severity(::Check) = 4 -synopsis(::Check) = "Avoid deep nesting of conditional statements" +Analysis.id(::Check) = "nesting-of-conditional-statements" +Analysis.severity(::Check) = 4 +Analysis.synopsis(::Check) = "Avoid deep nesting of conditional statements" const MAX_ALLOWED_NESTING_LEVELS = 3 const USER_MSG = "This conditional expression is too deeply nested (deeper than $MAX_ALLOWED_NESTING_LEVELS levels)." -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_flow_cntrl, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_flow_cntrl, n -> _check(this, ctxt, n)) end -function check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) @assert is_flow_cntrl(node) "Expected a flow control node, got [$(kind(node))]." # Count the nesting level of conditional statements - if conditional_nesting_level(node) > MAX_ALLOWED_NESTING_LEVELS + if _conditional_nesting_level(node) > MAX_ALLOWED_NESTING_LEVELS length_of_keyword = length(string(kind(node))) report_violation(ctxt, this, node, USER_MSG; offsetspan=(0, length_of_keyword)) end end -function conditional_nesting_level(node::SyntaxNode)::Int +function _conditional_nesting_level(node::SyntaxNode)::Int level = 0 while !isnothing(node) && !is_stop_point(node) if is_flow_cntrl(node) diff --git a/checks/NewlineAtFileEnd.jl b/checks/NewlineAtFileEnd.jl index 617943d..6c0035d 100644 --- a/checks/NewlineAtFileEnd.jl +++ b/checks/NewlineAtFileEnd.jl @@ -7,11 +7,11 @@ using ...WhitespaceHelpers: get_line_range include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "newline-at-file-end" -severity(::Check) = 7 -synopsis(::Check) = "Single newline at the end of file" +Analysis.id(::Check) = "newline-at-file-end" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Single newline at the end of file" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_toplevel, n -> _check(this, ctxt, n)) return nothing end diff --git a/checks/NoWhitespaceAroundTypeOperators.jl b/checks/NoWhitespaceAroundTypeOperators.jl index 8526c15..bd96c06 100644 --- a/checks/NoWhitespaceAroundTypeOperators.jl +++ b/checks/NoWhitespaceAroundTypeOperators.jl @@ -1,29 +1,28 @@ module NoWhitespaceAroundTypeOperators -using JuliaSyntax: first_byte, last_byte, is_prefix_call, is_prefix_op_call -using ...Properties: is_toplevel +using JuliaSyntax: first_byte, last_byte, is_prefix_op_call include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "no-whitespace-around-type-operators" -severity(::Check) = 7 -synopsis(::Check) = "Do not add whitespace around type operators" +Analysis.id(::Check) = "no-whitespace-around-type-operators" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Do not add whitespace around type operators" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_type_assertion_or_constraint, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, _is_type_assertion_or_constraint, n -> _check(this, ctxt, n)) end -function is_type_assertion_or_constraint(node) +function _is_type_assertion_or_constraint(node) return kind(node) in KSet":: <: >:" end -function check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) code = node.source.code if is_prefix_op_call(node) start = node.position last = first_byte(node.children[1]) - else + else if length(node.children) != 2 @warn "Expected a node with two children, got [$(length(node.children))]." node return @@ -34,10 +33,10 @@ function check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) text_between = code[start:last] if any(isspace, text_between) linepos = source_location(node.source, start) - report_violation(ctxt, this, + report_violation(ctxt, this, linepos, range(start, length=length(text_between)), - "Omit whitespace around this operator" + "Omit whitespace around this operator" ) end end diff --git a/checks/OmitTrailingWhiteSpace.jl b/checks/OmitTrailingWhiteSpace.jl index c9ce483..a1ce802 100644 --- a/checks/OmitTrailingWhiteSpace.jl +++ b/checks/OmitTrailingWhiteSpace.jl @@ -5,15 +5,15 @@ include("_common.jl") using ...Properties: is_toplevel struct Check<:Analysis.Check end -id(::Check) = "omit-trailing-white-space" -severity(::Check) = 7 -synopsis(::Check) = "Omit spaces at the end of a line" +Analysis.id(::Check) = "omit-trailing-white-space" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Omit spaces at the end of a line" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_toplevel, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_toplevel, n -> _check(this, ctxt, n)) end -function check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) code = node.source.code for m in eachmatch(r"( +)\r?\n", code) line::Int = count("\n", code[1:m.offset]) + 1 diff --git a/checks/OneExpressionPerLine.jl b/checks/OneExpressionPerLine.jl index c3e0a33..06dd157 100644 --- a/checks/OneExpressionPerLine.jl +++ b/checks/OneExpressionPerLine.jl @@ -7,11 +7,11 @@ using ...WhitespaceHelpers: get_line_range include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "one-expression-per-line" -severity(::Check) = 7 -synopsis(::Check) = "The number of expressions per line is limited to one." +Analysis.id(::Check) = "one-expression-per-line" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "The number of expressions per line is limited to one." -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> n == ctxt.rootNode, n -> _check(this, ctxt, n)) return nothing end diff --git a/checks/PreferConstVariablesOverNonConstGlobalVariables.jl b/checks/PreferConstVariablesOverNonConstGlobalVariables.jl index 27fe22b..8b6127d 100644 --- a/checks/PreferConstVariablesOverNonConstGlobalVariables.jl +++ b/checks/PreferConstVariablesOverNonConstGlobalVariables.jl @@ -6,11 +6,11 @@ using ...Properties: is_assignment, is_global_decl using ...SyntaxNodeHelpers: ancestors, get_all_assignees, is_scope_construct struct Check<:Analysis.Check end -id(::Check) = "prefer-const-variables-over-non-const-global-variables" -severity(::Check) = 3 -synopsis(::Check) = "Prefer const variables over non-const global variables" +Analysis.id(::Check) = "prefer-const-variables-over-non-const-global-variables" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Prefer const variables over non-const global variables" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> is_global_decl(n) && is_assignment(n), node -> begin ancs = ancestors(node) head = ancs[1:something(findfirst(is_scope_construct, ancs), length(ancs))] diff --git a/checks/PrefixOfAbstractTypeNames.jl b/checks/PrefixOfAbstractTypeNames.jl index 09d5c2f..624befa 100644 --- a/checks/PrefixOfAbstractTypeNames.jl +++ b/checks/PrefixOfAbstractTypeNames.jl @@ -4,18 +4,18 @@ include("_common.jl") using ...Properties: find_lhs_of_kind, is_upper_camel_case struct Check<:Analysis.Check end -id(::Check) = "prefix-of-abstract-type-names" -severity(::Check) = 4 -synopsis(::Check) = "Abstract type names should be prefixed by \"Abstract\"." +Analysis.id(::Check) = "prefix-of-abstract-type-names" +Analysis.severity(::Check) = 4 +Analysis.synopsis(::Check) = "Abstract type names should be prefixed by \"Abstract\"." -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, n -> kind(n) == K"abstract", node -> check(this, ctxt, node)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, n -> kind(n) == K"abstract", node -> _check(this, ctxt, node)) end -function check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) @assert kind(node) == K"abstract" "Expected an [abstract] node, got $(kind(node))" type_id = find_lhs_of_kind(K"Identifier", node) - @assert type_id !== nothing "Got a type declaration without name (identifier)." + @assert ! isnothing(type_id) "Got a type declaration without name (identifier)." type_name = string(type_id) if ! startswith(type_name, "Abstract") report_violation(ctxt, this, type_id, diff --git a/checks/ShortHandFunctionTooComplicated.jl b/checks/ShortHandFunctionTooComplicated.jl index 7b54ada..a75413c 100644 --- a/checks/ShortHandFunctionTooComplicated.jl +++ b/checks/ShortHandFunctionTooComplicated.jl @@ -5,24 +5,24 @@ using JuliaSyntax: sourcetext using ...Properties: MAX_LINE_LENGTH, expr_depth, expr_size, get_func_name, get_func_body struct Check<:Analysis.Check end -id(::Check) = "short-hand-function-too-complicated" -severity(::Check) = 3 -synopsis(::Check) = "Short-hand notation with concise functions" +Analysis.id(::Check) = "short-hand-function-too-complicated" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Short-hand notation with concise functions" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) == K"function", func -> begin body = get_func_body(func) if !isnothing(body) && kind(body) != K"block" - checkFunction(this, ctxt, func, body) + _check(this, ctxt, func, body) end end) end -function checkFunction(this::Check, ctxt::AnalysisContext, func::SyntaxNode, body::SyntaxNode) - report() = report_violation(ctxt, this, body, +function _check(this::Check, ctxt::AnalysisContext, func::SyntaxNode, body::SyntaxNode) + report() = report_violation(ctxt, this, body, "Function '$(get_func_name(func))' is too complex for the shorthand notation; use keyword 'function'." ) - + line_len = length(sourcetext(func)) if line_len > MAX_LINE_LENGTH report() diff --git a/checks/SingleModuleFile.jl b/checks/SingleModuleFile.jl index 98c6025..ce060a6 100644 --- a/checks/SingleModuleFile.jl +++ b/checks/SingleModuleFile.jl @@ -5,15 +5,15 @@ using JuliaSyntax: filename using ...Properties: is_module struct Check<:Analysis.Check end -id(::Check) = "single-module-file" -severity(::Check) = 5 -synopsis(::Check) = "Single module per file" +Analysis.id(::Check) = "single-module-file" +Analysis.severity(::Check) = 5 +Analysis.synopsis(::Check) = "Single module per file" -function init(this::Check, ctxt::AnalysisContext) - register_syntaxnode_action(ctxt, is_module, node -> check(this, ctxt, node)) +function Analysis.init(this::Check, ctxt::AnalysisContext) + register_syntaxnode_action(ctxt, is_module, node -> _check(this, ctxt, node)) end -function check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode) @assert kind(modjule) == K"module" "Expected a [module] node, got [$(kind(node))]." father = modjule.parent kids = children(father) diff --git a/checks/SingleSpaceAfterCommasAndSemicolons.jl b/checks/SingleSpaceAfterCommasAndSemicolons.jl index e8446b5..c28b92a 100644 --- a/checks/SingleSpaceAfterCommasAndSemicolons.jl +++ b/checks/SingleSpaceAfterCommasAndSemicolons.jl @@ -6,18 +6,18 @@ using ...Properties: is_toplevel using ...SyntaxNodeHelpers struct Check<:Analysis.Check end -id(::Check) = "single-space-after-commas-and-semicolons" -severity(::Check) = 7 -synopsis(::Check) = "Commas and semicolons are followed, but not preceded, by a space." +Analysis.id(::Check) = "single-space-after-commas-and-semicolons" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Commas and semicolons are followed, but not preceded, by a space." -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_toplevel, node -> begin code = node.source.code report_if_space(pos::Integer, func::Function, shouldhave::Integer, msg::String) = begin range = func(code, pos) if length(range) != shouldhave && !contains(code[range], '\n') # skip check if range contains a newline - report_violation(ctxt, this, - source_location(node.source, range.start), + report_violation(ctxt, this, + source_location(node.source, range.start), range, msg ) @@ -27,14 +27,14 @@ function init(this::Check, ctxt::AnalysisContext) pos = m.offset leaf = find_greenleaf(ctxt, pos) # Find the GreenLeaf containing the character if kind(leaf.node) ∉ KSet"Char Comment String" # Skip strings and comments - report_if_space(pos, find_whitespace_func(false), 0, "Unexpected whitespace") - report_if_space(pos, find_whitespace_func(true), 1, "Expected single whitespace") + report_if_space(pos, _find_whitespace_func(false), 0, "Unexpected whitespace") + report_if_space(pos, _find_whitespace_func(true), 1, "Expected single whitespace") end end end) end -function find_whitespace_func(forward::Bool)::Function +function _find_whitespace_func(forward::Bool)::Function find = forward ? nextind : prevind return (s::AbstractString, start::Integer) -> begin p = find(s, start) diff --git a/checks/SpaceAroundBinaryInfixOperators.jl b/checks/SpaceAroundBinaryInfixOperators.jl index 9c73299..80d6720 100644 --- a/checks/SpaceAroundBinaryInfixOperators.jl +++ b/checks/SpaceAroundBinaryInfixOperators.jl @@ -18,11 +18,9 @@ using ...WhitespaceHelpers: include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "space-around-binary-infix-operators" -severity(::Check) = 7 -function synopsis(::Check) - return "Selected binary infix operators and the = character are followed and preceded by a single space." -end +Analysis.id(::Check) = "space-around-binary-infix-operators" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Selected binary infix operators and the = character are followed and preceded by a single space." """ Kinds for which the rule will assert no whitespace surrounds them @@ -33,7 +31,7 @@ Kinds for which the surrounding whitespace is not checked """ const EXCLUDED_KINDS = [":", "."] -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action( ctxt, _node_applicable, diff --git a/checks/StructMembersAreInLowerSnakeCase.jl b/checks/StructMembersAreInLowerSnakeCase.jl index 9b7f2cf..7216c29 100644 --- a/checks/StructMembersAreInLowerSnakeCase.jl +++ b/checks/StructMembersAreInLowerSnakeCase.jl @@ -5,19 +5,19 @@ include("_common.jl") using ...Properties: find_lhs_of_kind, is_lower_snake, get_struct_members struct Check<:Analysis.Check end -id(::Check) = "struct-members-are-in-lower-snake-case" -severity(::Check) = 8 -synopsis(::Check) = "Struct members should be in \"lower_snake_case\"." +Analysis.id(::Check) = "struct-members-are-in-lower-snake-case" +Analysis.severity(::Check) = 8 +Analysis.synopsis(::Check) = "Struct members should be in \"lower_snake_case\"." -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) === K"struct", node -> begin for field in get_struct_members(node) - check(this, ctxt, field) + _check(this, ctxt, field) end end) end -function check(this::Check, ctxt::AnalysisContext, field::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, field::SyntaxNode) @assert kind(field.parent) == K"block" && kind(field.parent.parent) == K"struct" "Expected a node representing" * " a field (child of a [struct])" field.parent diff --git a/checks/TooManyTypesInUnions.jl b/checks/TooManyTypesInUnions.jl index 54df9e9..34c96c0 100644 --- a/checks/TooManyTypesInUnions.jl +++ b/checks/TooManyTypesInUnions.jl @@ -4,13 +4,13 @@ include("_common.jl") using ...Properties: is_union_decl struct Check<:Analysis.Check end -id(::Check) = "too-many-types-in-unions" -severity(::Check) = 6 -synopsis(::Check) = "Too many types in Unions" +Analysis.id(::Check) = "too-many-types-in-unions" +Analysis.severity(::Check) = 6 +Analysis.synopsis(::Check) = "Too many types in Unions" const MAX_UNION_TYPES = 4 -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_union_decl, node -> begin local union_types = children(node)[2:end] # discard the 1st, which is "Union" local count = length(union_types) @@ -20,4 +20,4 @@ function init(this::Check, ctxt::AnalysisContext) end) end -end +end # module TooManyTypesInUnions diff --git a/checks/TypeNamesUpperCamelCase.jl b/checks/TypeNamesUpperCamelCase.jl index 13eb856..9d449b4 100644 --- a/checks/TypeNamesUpperCamelCase.jl +++ b/checks/TypeNamesUpperCamelCase.jl @@ -5,14 +5,14 @@ include("_common.jl") using ...Properties: is_upper_camel_case, find_lhs_of_kind struct Check<:Analysis.Check end -id(::Check) = "type-names-upper-camel-case" -severity(::Check) = 3 -synopsis(::Check) = "Type names should be in \"UpperCamelCase\"" +Analysis.id(::Check) = "type-names-upper-camel-case" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Type names should be in \"UpperCamelCase\"" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) ∈ KSet"abstract struct", node -> begin identifier = find_lhs_of_kind(K"Identifier", node) - if identifier !== nothing + if ! isnothing(identifier) name = string(identifier) if ! is_upper_camel_case(name) report_violation(ctxt, this, identifier, "Type names such as '$name' should be written in \"UpperCamelCase\".") diff --git a/checks/UnderscorePrefixForPrivateFunctions.jl b/checks/UnderscorePrefixForPrivateFunctions.jl index 2645c58..b5ed7fd 100644 --- a/checks/UnderscorePrefixForPrivateFunctions.jl +++ b/checks/UnderscorePrefixForPrivateFunctions.jl @@ -5,11 +5,11 @@ include("_common.jl") using ...Properties: get_func_name, is_export, is_function, is_module struct Check<:Analysis.Check end -id(::Check) = "underscore-prefix-for-private-functions" -severity(::Check) = 8 -synopsis(::Check) = "Private functions are prefixed with one underscore _ character." +Analysis.id(::Check) = "underscore-prefix-for-private-functions" +Analysis.severity(::Check) = 8 +Analysis.synopsis(::Check) = "Private functions are prefixed with one underscore _ character." -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) return nothing end diff --git a/checks/UseAmericanEnglish.jl b/checks/UseAmericanEnglish.jl index 71a536a..dacb417 100644 --- a/checks/UseAmericanEnglish.jl +++ b/checks/UseAmericanEnglish.jl @@ -6,11 +6,11 @@ using JuliaSyntax: byte_range include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "use-american-english" -severity(::Check) = 9 -synopsis(::Check) = "Comments should be in the American-English language" +Analysis.id(::Check) = "use-american-english" +Analysis.severity(::Check) = 9 +Analysis.synopsis(::Check) = "Comments should be in the American-English language" -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing forbidden_words = _load_words(joinpath(@__DIR__, "_config", "words_en-GB.txt")) register_syntaxnode_action(ctxt, contains_comments, n -> _check_comment(this, ctxt, n, forbidden_words)) register_syntaxnode_action(ctxt, n -> kind(n) == K"doc", n -> _check_docstring(this, ctxt, n, forbidden_words)) diff --git a/checks/UseEachindexToIterateIndices.jl b/checks/UseEachindexToIterateIndices.jl index 234f3da..1084668 100644 --- a/checks/UseEachindexToIterateIndices.jl +++ b/checks/UseEachindexToIterateIndices.jl @@ -7,11 +7,11 @@ using ...Properties: get_iteration_parts, is_range using ...SyntaxNodeHelpers: find_descendants struct Check<:Analysis.Check end -id(::Check) = "use-eachindex-to-iterate-indices" -severity(::Check) = 5 -synopsis(::Check) = "Use eachindex() instead of a constructed range for iteration over a collection." +Analysis.id(::Check) = "use-eachindex-to-iterate-indices" +Analysis.severity(::Check) = 5 +Analysis.synopsis(::Check) = "Use eachindex() instead of a constructed range for iteration over a collection." -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) == K"for", node -> _check(this, ctxt, node)) end diff --git a/checks/UseIsinfToCheckForInfinite.jl b/checks/UseIsinfToCheckForInfinite.jl index 732673f..7816570 100644 --- a/checks/UseIsinfToCheckForInfinite.jl +++ b/checks/UseIsinfToCheckForInfinite.jl @@ -6,11 +6,11 @@ using ...SyntaxNodeHelpers include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "use-isinf-to-check-for-infinite" -severity(::Check) = 3 -synopsis(::Check) = "Use isinf to check for infinite values" +Analysis.id(::Check) = "use-isinf-to-check-for-infinite" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Use isinf to check for infinite values" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_eq_neq_comparison, node -> begin apply_to_operands(node, node -> begin if extract_special_value(node) ∈ SyntaxNodeHelpers.INF_VALUES diff --git a/checks/UseIsmissingToCheckForMissingValues.jl b/checks/UseIsmissingToCheckForMissingValues.jl index e9c6828..afcdaff 100644 --- a/checks/UseIsmissingToCheckForMissingValues.jl +++ b/checks/UseIsmissingToCheckForMissingValues.jl @@ -6,11 +6,11 @@ using ...SyntaxNodeHelpers include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "use-ismissing-to-check-for-missing-values" -severity(::Check) = 3 -synopsis(::Check) = "Use ismissing to check for missing values" +Analysis.id(::Check) = "use-ismissing-to-check-for-missing-values" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Use ismissing to check for missing values" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_eq_neq_comparison, node -> begin apply_to_operands(node, node -> begin if extract_special_value(node) ∈ SyntaxNodeHelpers.MISSING_VALUES diff --git a/checks/UseIsnanToCheckForNan.jl b/checks/UseIsnanToCheckForNan.jl index c423d4e..2b0074d 100644 --- a/checks/UseIsnanToCheckForNan.jl +++ b/checks/UseIsnanToCheckForNan.jl @@ -6,11 +6,11 @@ using ...SyntaxNodeHelpers include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "use-isnan-to-check-for-nan" -severity(::Check) = 3 -synopsis(::Check) = "Use isnan to check for not-a-number values" +Analysis.id(::Check) = "use-isnan-to-check-for-nan" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Use isnan to check for not-a-number values" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, is_eq_neq_comparison, node -> begin apply_to_operands(node, node -> begin if extract_special_value(node) ∈ SyntaxNodeHelpers.NAN_VALUES diff --git a/checks/UseIsnothingToCheckForNothingValues.jl b/checks/UseIsnothingToCheckForNothingValues.jl index d323c53..1a7b269 100644 --- a/checks/UseIsnothingToCheckForNothingValues.jl +++ b/checks/UseIsnothingToCheckForNothingValues.jl @@ -6,11 +6,11 @@ using ...Properties: is_eq_neq_comparison using ...SyntaxNodeHelpers: apply_to_operands, extract_special_value struct Check<:Analysis.Check end -id(::Check) = "use-isnothing-to-check-for-nothing-values" -severity(::Check) = 3 -synopsis(::Check) = "Use isnothing to check variables for nothing" +Analysis.id(::Check) = "use-isnothing-to-check-for-nothing-values" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Use isnothing to check variables for nothing" -function init(this::Check, ctxt::AnalysisContext)::Nothing +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_eq_neq_comparison, node -> begin apply_to_operands(node, node -> begin if extract_special_value(node) == "nothing" diff --git a/checks/UseSpacesInsteadOfTabs.jl b/checks/UseSpacesInsteadOfTabs.jl index 0360603..e0a41cc 100644 --- a/checks/UseSpacesInsteadOfTabs.jl +++ b/checks/UseSpacesInsteadOfTabs.jl @@ -3,13 +3,13 @@ module UseSpacesInsteadOfTabs include("_common.jl") struct Check<:Analysis.Check end -id(::Check) = "use-spaces-instead-of-tabs" -severity(::Check) = 7 -synopsis(::Check) = "Use spaces instead of tabs for indentation" +Analysis.id(::Check) = "use-spaces-instead-of-tabs" +Analysis.severity(::Check) = 7 +Analysis.synopsis(::Check) = "Use spaces instead of tabs for indentation" const REGEX = r"(\s*)\t+.*" -function init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext) register_syntaxnode_action(ctxt, n -> kind(n) == K"toplevel", node -> begin code = node.source.code starts = node.source.line_starts @@ -18,7 +18,7 @@ function init(this::Check, ctxt::AnalysisContext) for (start,stop) in successive_pairs line::String = code[start:prevind(code, stop)] m = match(REGEX, line) - if m !== nothing + if ! isnothing(m) offset::Int = length(m.captures[1]) linepos = (linenr, offset+1) bufferrange = range(start + offset, length=1) diff --git a/checks/VariablesHaveFixedTypes.jl b/checks/VariablesHaveFixedTypes.jl index c32fcc0..6b82d0e 100644 --- a/checks/VariablesHaveFixedTypes.jl +++ b/checks/VariablesHaveFixedTypes.jl @@ -21,16 +21,16 @@ and doing this completely would be a _lot_ of specific cases. =# struct Check<:Analysis.Check end -id(::Check) = "variables-have-fixed-types" -severity(::Check) = 3 -synopsis(::Check) = "Types of variables should not change." +Analysis.id(::Check) = "variables-have-fixed-types" +Analysis.severity(::Check) = 3 +Analysis.synopsis(::Check) = "Types of variables should not change." -function init(this::Check, ctxt::AnalysisContext)::Nothing - register_syntaxnode_action(ctxt, is_assignment, n -> check(this, ctxt, n)) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing + register_syntaxnode_action(ctxt, is_assignment, n -> _check(this, ctxt, n)) return nothing end -function check(this::Check, ctxt::AnalysisContext, assignment_node::SyntaxNode)::Nothing +function _check(this::Check, ctxt::AnalysisContext, assignment_node::SyntaxNode)::Nothing if type_has_changed_from_init(ctxt.symboltable, assignment_node) assigned_variable = get_var_from_assignment(assignment_node) if isnothing(assigned_variable) diff --git a/checks/_common.jl b/checks/_common.jl index d9d0e3b..dd6784e 100644 --- a/checks/_common.jl +++ b/checks/_common.jl @@ -1,3 +1,2 @@ -import ..Analysis: id, init, synopsis, severity using ..Analysis using JuliaSyntax: SyntaxNode, @K_str, @KSet_str, kind, children, numchildren, source_location diff --git a/src/Analysis.jl b/src/Analysis.jl index e3f0cd5..0fc5c0a 100644 --- a/src/Analysis.jl +++ b/src/Analysis.jl @@ -84,7 +84,7 @@ end function _get_green_leaves!(list::Vector{GreenLeaf}, sf::SourceFile, node::GreenNode, pos::Int) cs = children(node) - if cs === nothing + if isnothing(cs) rng = range(pos, prevind(sf.code, pos + node.span)) push!(list, GreenLeaf(sf, node, rng)) return @@ -117,7 +117,7 @@ function find_syntaxnode_at_position(node::SyntaxNode, pos::Integer)::Union{Synt # Iterate through children to find a more specific node. for child in something(children(node), []) found_child = find_syntaxnode_at_position(child, pos) - if found_child !== nothing + if ! isnothing(found_child) return found_child end end @@ -153,7 +153,7 @@ function report_violation(ctxt::AnalysisContext, check::Check, node::SyntaxNode, linepos = JuliaSyntax.source_location(node) bufferrange = JuliaSyntax.byte_range(node) - if offsetspan !== nothing + if ! isnothing(offsetspan) bufferrange = range(bufferrange.start + offsetspan[1], length=offsetspan[2]) end @@ -211,7 +211,7 @@ function dfs_traversal(ctxt::AnalysisContext, node::SyntaxNode, visitor_func::Fu # 3. Recursively visit children local children = JuliaSyntax.children(node) - if children === nothing + if isnothing(children) return end for child_node in children diff --git a/src/CommentHelpers.jl b/src/CommentHelpers.jl index a5d6ea9..add35d7 100644 --- a/src/CommentHelpers.jl +++ b/src/CommentHelpers.jl @@ -3,7 +3,7 @@ module CommentHelpers using JuliaSyntax: @K_str, @KSet_str, SyntaxNode, kind, child_position_span, view, JuliaSyntax as JS using ..WhitespaceHelpers: combine_ranges, normalized_green_child_range -export Comment, CommentBlock, get_comment_blocks, get_range, get_text, contains_comments +export Comment, CommentBlock, get_comments, get_comment_blocks, get_range, get_text, contains_comments struct Comment range::UnitRange diff --git a/src/SymbolTable.jl b/src/SymbolTable.jl index b27882a..5cb70a4 100644 --- a/src/SymbolTable.jl +++ b/src/SymbolTable.jl @@ -134,11 +134,13 @@ scopes_within_module(table::SymbolTableStruct)::NestedScopes = _current_module(t _current_module(table::SymbolTableStruct)::Module = first(table.stack) -# TODO: a file can be `include`d into another, thus into another -# module and, what is most important from the point of view of the -# symbols table and declarations: something can be declared outside -# the file under analysis, and we will surely get confused about its -# scope. +#= +TODO: a file can be `include`d into another, thus into another +module and, what is most important from the point of view of the +symbols table and declarations: something can be declared outside +the file under analysis, and we will surely get confused about its +scope. +=# function _enter_scope!(table::SymbolTableStruct) push!(scopes_within_module(table), Scope()) @@ -365,4 +367,4 @@ function print_state(table::SymbolTableStruct)::String return state end -end +end # module SymbolTable diff --git a/src/SyntaxNodeHelpers.jl b/src/SyntaxNodeHelpers.jl index 0df5f9f..7fe95bc 100644 --- a/src/SyntaxNodeHelpers.jl +++ b/src/SyntaxNodeHelpers.jl @@ -113,7 +113,7 @@ function find_node_at_position(node::SyntaxNode, pos::Integer)::Union{SyntaxNode # Search through children to find the most specific node for child in something(children(node), []) found_child = find_node_at_position(child, pos) - if found_child !== nothing + if !isnothing(found_child) return found_child end end diff --git a/src/TypeHelpers.jl b/src/TypeHelpers.jl index 422cf90..f29eb72 100644 --- a/src/TypeHelpers.jl +++ b/src/TypeHelpers.jl @@ -24,9 +24,9 @@ Currently, possible literals in JuliaSyntax are: "Char" "CmdString" -All of these are returned as kinds, and are flagged by the is_literal function. -=# -TypeSpecifier = Union{String, Nothing} +All of these are returned as kinds, and are flagged by the is_literal function. +=# +const TypeSpecifier = Union{String, Nothing} function is_different_type(type_1::TypeSpecifier, type_2::TypeSpecifier)::Bool if isnothing(type_1) || isnothing(type_2) @@ -38,7 +38,7 @@ end """ Tries to find the type of the associated variable from a node. -For now, this only covers assignments of the form +For now, this only covers assignments of the form a = /something/. """ function get_variable_type_from_node(node::SyntaxNode)::TypeSpecifier From 8682613d6e0d08b756edae60bded5ad90c4ca90b Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Fri, 23 Jan 2026 09:08:33 +0100 Subject: [PATCH 02/10] Solve violations on `long-form-functions-have-a-terminating-return-statement` There are two remaining violations that I believe are false positives. Going to fix the rule in another PR. --- checks/AvoidGlobalVariables.jl | 3 +- checks/AvoidHardCodedNumbers.jl | 6 ++- checks/DoNotChangeGeneratedIndices.jl | 8 ++-- checks/DoNotCommentOutCode.jl | 4 +- checks/DoNotNestMultilineComments.jl | 3 +- checks/DoNotSetVariablesToInf.jl | 3 +- checks/DoNotSetVariablesToNan.jl | 3 +- checks/DocumentConstants.jl | 6 ++- ...ationMarkInFunctionIdentifierIfMutating.jl | 6 ++- checks/FunctionArgumentsLowerSnakeCase.jl | 6 ++- checks/FunctionIdentifiersInLowerSnakeCase.jl | 6 ++- .../FunctionsMutateOnlyZeroOrOneArguments.jl | 6 ++- checks/GlobalVariablesUpperSnakeCase.jl | 3 +- checks/ImplementUnionsAsConsts.jl | 3 +- checks/IndentationOfModules.jl | 1 + checks/InfiniteWhileLoop.jl | 3 +- checks/LeadingAndTrailingDigits.jl | 3 +- checks/LocationOfGlobalVariables.jl | 3 +- ...unctionsHaveATerminatingReturnStatement.jl | 5 ++- checks/ModuleEndComment.jl | 5 ++- checks/ModuleExportLocation.jl | 3 +- checks/ModuleImportLocation.jl | 3 +- checks/ModuleIncludeLocation.jl | 3 +- checks/ModuleSingleImportLine.jl | 3 +- checks/NestingOfConditionalStatements.jl | 6 ++- checks/NewlineAtFileEnd.jl | 2 +- checks/NoWhitespaceAroundTypeOperators.jl | 8 ++-- checks/OmitTrailingWhiteSpace.jl | 6 ++- ...nstVariablesOverNonConstGlobalVariables.jl | 3 +- checks/PrefixOfAbstractTypeNames.jl | 6 ++- checks/ShortHandFunctionTooComplicated.jl | 5 ++- checks/SingleModuleFile.jl | 6 ++- checks/SingleSpaceAfterCommasAndSemicolons.jl | 6 ++- checks/StructMembersAreInLowerSnakeCase.jl | 6 ++- checks/TooManyTypesInUnions.jl | 3 +- checks/TypeNamesUpperCamelCase.jl | 3 +- checks/UseEachindexToIterateIndices.jl | 3 +- checks/UseIsinfToCheckForInfinite.jl | 3 +- checks/UseIsmissingToCheckForMissingValues.jl | 3 +- checks/UseIsnanToCheckForNan.jl | 3 +- checks/UseSpacesInsteadOfTabs.jl | 3 +- src/Analysis.jl | 8 ++-- src/JuliaCheck.jl | 3 +- src/MutatingFunctionsHelpers.jl | 3 +- src/Properties.jl | 12 +++--- src/SymbolTable.jl | 38 ++++++++++++------- 46 files changed, 152 insertions(+), 85 deletions(-) diff --git a/checks/AvoidGlobalVariables.jl b/checks/AvoidGlobalVariables.jl index e5d6030..981c7ab 100644 --- a/checks/AvoidGlobalVariables.jl +++ b/checks/AvoidGlobalVariables.jl @@ -14,7 +14,7 @@ Analysis.id(::Check) = "avoid-global-variables" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Avoid global variables when possible" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_global_decl, node -> begin id = find_lhs_of_kind(K"Identifier", node) if isnothing(id) @@ -35,6 +35,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) report_violation(ctxt, this, id, synopsis(this)) return nothing end) + return nothing end end # module AvoidGlobalVariables diff --git a/checks/AvoidHardCodedNumbers.jl b/checks/AvoidHardCodedNumbers.jl index ca7543f..efdd837 100644 --- a/checks/AvoidHardCodedNumbers.jl +++ b/checks/AvoidHardCodedNumbers.jl @@ -15,12 +15,13 @@ Analysis.id(::Check) = "avoid-hard-coded-numbers" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Avoid hard-coded numbers" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_literal_number, n -> _check(this, ctxt, n)) + return nothing end # Also FIXME: should I use all the 64 bits versions of the types? -function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing @assert is_literal_number(node) "Expected a node with a literal number, got $(kind(node))" if !_is_const_declaration(node) && !_in_array_assignment(node) && _is_magic_number(node) n = get_number(node) @@ -30,6 +31,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) push!(this.seen_before, n) end end + return nothing end """ diff --git a/checks/DoNotChangeGeneratedIndices.jl b/checks/DoNotChangeGeneratedIndices.jl index fb0747d..c4050c7 100644 --- a/checks/DoNotChangeGeneratedIndices.jl +++ b/checks/DoNotChangeGeneratedIndices.jl @@ -10,11 +10,12 @@ Analysis.id(::Check) = "do-not-change-generated-indices" Analysis.severity(::Check) = 5 Analysis.synopsis(::Check) = "Do not change generated indices" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"for", n -> _check_for_loop(this, ctxt, n)) + return nothing end -function _check_for_loop(this::Check, ctxt::AnalysisContext, for_loop::SyntaxNode) +function _check_for_loop(this::Check, ctxt::AnalysisContext, for_loop::SyntaxNode)::Nothing loop_var, iter_expr = get_iteration_parts(for_loop) if isnothing(loop_var) || isnothing(iter_expr) return nothing @@ -31,9 +32,10 @@ function _check_for_loop(this::Check, ctxt::AnalysisContext, for_loop::SyntaxNod body = children(for_loop)[2] _frisk_for_modification(this, ctxt, body, var_name) end + return nothing end -function _loop_var_to_string(var::SyntaxNode) +function _loop_var_to_string(var::SyntaxNode)::String x = var if kind(x) == K"tuple" x = first_child(x) end if kind(x) == K"Identifier" return string(x) end diff --git a/checks/DoNotCommentOutCode.jl b/checks/DoNotCommentOutCode.jl index 0822cae..e8d4e0a 100644 --- a/checks/DoNotCommentOutCode.jl +++ b/checks/DoNotCommentOutCode.jl @@ -44,9 +44,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing return nothing end -function _report(ctxt::AnalysisContext, this::Check, range::UnitRange) - report_violation(ctxt, this, range, "Comment contains code") -end +_report(ctxt::AnalysisContext, this::Check, range::UnitRange) = report_violation(ctxt, this, range, "Comment contains code") # If JS can parse the comment contents, it must be code function _contains_code(text::AbstractString)::Bool diff --git a/checks/DoNotNestMultilineComments.jl b/checks/DoNotNestMultilineComments.jl index e9c4cd4..6bd24e4 100644 --- a/checks/DoNotNestMultilineComments.jl +++ b/checks/DoNotNestMultilineComments.jl @@ -12,7 +12,7 @@ Analysis.synopsis(::Check) = "Don't nest multiline comments" const ML_COMMENT = "#=" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_toplevel, node -> begin code = node.source.code comments = filter(gl -> kind(gl) == K"Comment", ctxt.greenleaves) @@ -33,6 +33,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end return nothing end) + return nothing end end # module DoNotNestMultilineComments diff --git a/checks/DoNotSetVariablesToInf.jl b/checks/DoNotSetVariablesToInf.jl index 7354ef9..8aa404c 100644 --- a/checks/DoNotSetVariablesToInf.jl +++ b/checks/DoNotSetVariablesToInf.jl @@ -9,7 +9,7 @@ Analysis.id(::Check) = "do-not-set-variables-to-inf" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Do not set variables to Inf, Inf16, Inf32 or Inf64" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"=", node -> begin if numchildren(node) != 2 @debug "Assignment with $(numchildren(node)) children instead of 2." @@ -22,6 +22,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end) + return nothing end end # module DoNotSetVariablesToInf diff --git a/checks/DoNotSetVariablesToNan.jl b/checks/DoNotSetVariablesToNan.jl index 2de685e..952f8b5 100644 --- a/checks/DoNotSetVariablesToNan.jl +++ b/checks/DoNotSetVariablesToNan.jl @@ -9,7 +9,7 @@ Analysis.id(::Check) = "do-not-set-variables-to-nan" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Do not set variables to NaN, NaN16, NaN32 or NaN64" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"=", node -> begin if numchildren(node) != 2 @debug "Assignment with $(numchildren(node)) children instead of 2." @@ -22,6 +22,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end) + return nothing end end # module DoNotSetVariablesToNan diff --git a/checks/DocumentConstants.jl b/checks/DocumentConstants.jl index c49a30a..e398bcb 100644 --- a/checks/DocumentConstants.jl +++ b/checks/DocumentConstants.jl @@ -9,11 +9,12 @@ Analysis.id(::Check) = "document-constants" Analysis.severity(::Check) = 7 Analysis.synopsis(::Check) = "Constants must have a docstring" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_constant, n -> _check(this, ctxt, n)) + return nothing end -function _check(this::Check, ctxt::AnalysisContext, const_node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, const_node::SyntaxNode)::Nothing if kind(const_node) == K"global" if haschildren(const_node) const_node = children(const_node)[1] @@ -37,6 +38,7 @@ function _check(this::Check, ctxt::AnalysisContext, const_node::SyntaxNode) @debug "An assignment without children:" assignment end end + return nothing end end # module DocumentConstants diff --git a/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl b/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl index 1d374b7..6e09a5e 100644 --- a/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl +++ b/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl @@ -11,13 +11,14 @@ Analysis.id(::Check) = "exclamation-mark-in-function-identifier-if-mutating" Analysis.severity(::Check) = 4 Analysis.synopsis(::Check) = "Only functions postfixed with an exclamation mark can mutate an argument." -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, _is_nonmutating_fn, n -> _check_function(this, ctxt, n)) + return nothing end _is_nonmutating_fn(n::SyntaxNode)::Bool = is_function(n) && !endswith(string(get_func_name(n)), "!") -function _check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode) +function _check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode)::Nothing func_arg_strings = get_string_fn_args(function_node) all_mutated_variables = get_mutated_variables_in_scope(ctxt, get_func_body(function_node)) for func_arg in func_arg_strings @@ -26,6 +27,7 @@ function _check_function(this::Check, ctxt::AnalysisContext, function_node::Synt "Function mutates argument $(string(func_arg)) without having an exclamation mark.") end end + return nothing end end # end ExclamationMarkInFunctionIdentifierIfMutating diff --git a/checks/FunctionArgumentsLowerSnakeCase.jl b/checks/FunctionArgumentsLowerSnakeCase.jl index 9c0b556..4d1ddc3 100644 --- a/checks/FunctionArgumentsLowerSnakeCase.jl +++ b/checks/FunctionArgumentsLowerSnakeCase.jl @@ -9,7 +9,7 @@ Analysis.id(::Check) = "function-arguments-lower-snake-case" Analysis.severity(::Check) = 7 Analysis.synopsis(::Check) = "Function arguments must be written in \"lower_snake_case\"" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"function", node -> begin fname = get_func_name(node) fname_str = string(fname) @@ -28,9 +28,10 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end end) + return nothing end -function _check_argument(this::Check, ctxt::AnalysisContext, f_name::AbstractString, f_arg::SyntaxNode) +function _check_argument(this::Check, ctxt::AnalysisContext, f_name::AbstractString, f_arg::SyntaxNode)::Nothing if kind(f_arg) == K"::" f_arg = numchildren(f_arg) == 1 ? nothing : children(f_arg)[1] end @@ -48,6 +49,7 @@ function _check_argument(this::Check, ctxt::AnalysisContext, f_name::AbstractStr "Argument '$arg_name' of function '$f_name' must be written in \"lower_snake_case\"." ) end + return nothing end end # module FunctionArgumentsLowerSnakeCase diff --git a/checks/FunctionIdentifiersInLowerSnakeCase.jl b/checks/FunctionIdentifiersInLowerSnakeCase.jl index d7d7fd0..8f896d9 100644 --- a/checks/FunctionIdentifiersInLowerSnakeCase.jl +++ b/checks/FunctionIdentifiersInLowerSnakeCase.jl @@ -9,7 +9,7 @@ Analysis.id(::Check) = "function-identifiers-in-lower-snake-case" Analysis.severity(::Check) = 8 Analysis.synopsis(::Check) = "Function name should be written in \"lower_snake_case\"" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"function", node -> begin fname = get_func_name(node) @@ -18,9 +18,10 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end _check_function_name(this, ctxt, fname) end) + return nothing end -function _check_function_name(this::Check, ctxt::AnalysisContext, func_name::SyntaxNode) +function _check_function_name(this::Check, ctxt::AnalysisContext, func_name::SyntaxNode)::Nothing @assert kind(func_name) == K"Identifier" "Expected an [Identifier] node, got [$(kind(func_name))]" if inside(func_name, is_struct) # Inner constructors (functions inside a type definition) must match the @@ -34,6 +35,7 @@ function _check_function_name(this::Check, ctxt::AnalysisContext, func_name::Syn "Function name $fname should be written in lower_snake_case." ) end + return nothing end end # module FunctionIdentifiersInLowerSnakeCase diff --git a/checks/FunctionsMutateOnlyZeroOrOneArguments.jl b/checks/FunctionsMutateOnlyZeroOrOneArguments.jl index 200f46a..4a86eb4 100644 --- a/checks/FunctionsMutateOnlyZeroOrOneArguments.jl +++ b/checks/FunctionsMutateOnlyZeroOrOneArguments.jl @@ -11,11 +11,12 @@ Analysis.id(::Check) = "functions-mutate-only-zero-or-one-arguments" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Functions should change only one or zero argument(s)." -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_function, n -> _check_function(this, ctxt, n)) + return nothing end -function _check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode) +function _check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode)::Nothing func_arg_nodes = get_flattened_fn_arg_nodes(function_node) all_mutated_variables = get_mutated_variables_in_scope(ctxt, get_func_body(function_node)) for func_arg in func_arg_nodes[2:end] @@ -25,6 +26,7 @@ function _check_function(this::Check, ctxt::AnalysisContext, function_node::Synt "Function mutates variable $(string(func_arg_string)) while it is not the first argument.") end end + return nothing end end # end FunctionsMutateOnlyZeroOrOneArguments diff --git a/checks/GlobalVariablesUpperSnakeCase.jl b/checks/GlobalVariablesUpperSnakeCase.jl index 8c1ae95..858f8f8 100644 --- a/checks/GlobalVariablesUpperSnakeCase.jl +++ b/checks/GlobalVariablesUpperSnakeCase.jl @@ -11,7 +11,7 @@ Analysis.id(::Check) = "global-variables-upper-snake-case" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Casing of globals" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> is_assignment(n) && !is_field_assignment(n), n -> begin ids = get_all_assignees(n) for id in ids @@ -28,6 +28,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end end) + return nothing end end # module GlobalVariablesUpperSnakeCase diff --git a/checks/ImplementUnionsAsConsts.jl b/checks/ImplementUnionsAsConsts.jl index f7c266c..b239db6 100644 --- a/checks/ImplementUnionsAsConsts.jl +++ b/checks/ImplementUnionsAsConsts.jl @@ -9,10 +9,11 @@ Analysis.id(::Check) = "implement-unions-as-consts" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Implement Unions as const" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_union_decl, node -> begin _check_union(this, ctxt, node) end) + return nothing end function _check_union(this::Check, ctxt::AnalysisContext, union::SyntaxNode)::Nothing diff --git a/checks/IndentationOfModules.jl b/checks/IndentationOfModules.jl index 94f3a8d..042ceaf 100644 --- a/checks/IndentationOfModules.jl +++ b/checks/IndentationOfModules.jl @@ -41,6 +41,7 @@ function _check(this::Check, ctxt::AnalysisContext, module_node::SyntaxNode)::No end end end + return nothing end end # module IndentationOfModules diff --git a/checks/InfiniteWhileLoop.jl b/checks/InfiniteWhileLoop.jl index 55e480c..8d51c94 100644 --- a/checks/InfiniteWhileLoop.jl +++ b/checks/InfiniteWhileLoop.jl @@ -7,8 +7,9 @@ Analysis.id(::Check) = "infinite-while-loop" Analysis.severity(::Check) = 5 Analysis.synopsis(::Check) = "Do not use while true" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"while", n -> _check_while_node(this, ctxt, n)) + return nothing end function _check_while_node(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing diff --git a/checks/LeadingAndTrailingDigits.jl b/checks/LeadingAndTrailingDigits.jl index d343636..9a281bb 100644 --- a/checks/LeadingAndTrailingDigits.jl +++ b/checks/LeadingAndTrailingDigits.jl @@ -9,8 +9,9 @@ Analysis.id(::Check) = "leading-and-trailing-digits" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Floating-point numbers should always have one digit before the decimal point and at least one after" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"Float", n -> _check_float_node(this, ctxt, n)) + return nothing end function _check_float_node(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing diff --git a/checks/LocationOfGlobalVariables.jl b/checks/LocationOfGlobalVariables.jl index 8c087b2..d9d4bfd 100644 --- a/checks/LocationOfGlobalVariables.jl +++ b/checks/LocationOfGlobalVariables.jl @@ -9,8 +9,9 @@ Analysis.id(::Check) = "location-of-global-variables" Analysis.severity(::Check) = 7 Analysis.synopsis(::Check) = "Global variables should be placed at the top of a module or file" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_global_decl, n -> _check(this, ctxt, n)) + return nothing end function _check(this::Check, ctxt::AnalysisContext, glob_decl::SyntaxNode)::Nothing diff --git a/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl b/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl index 1357207..70e907d 100644 --- a/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl +++ b/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl @@ -10,13 +10,14 @@ Analysis.id(::Check) = "long-form-functions-have-a-terminating-return-statement" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Long form functions should end with an explicit return statement" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"function", node -> begin body = get_func_body(node) - if body !== nothing + if ! isnothing(body) _check(this, ctxt, body) end end) + return nothing end function _check(this::Check, ctxt::AnalysisContext, func_body::SyntaxNode)::Nothing diff --git a/checks/ModuleEndComment.jl b/checks/ModuleEndComment.jl index 854f730..cdbd582 100644 --- a/checks/ModuleEndComment.jl +++ b/checks/ModuleEndComment.jl @@ -10,8 +10,9 @@ Analysis.id(::Check) = "module-end-comment" Analysis.severity(::Check) = 9 Analysis.synopsis(::Check) = "The end statement of a module should have a comment with the module name" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) + return nothing end function _check(this::Check, ctxt::AnalysisContext, mod::SyntaxNode)::Nothing @@ -34,7 +35,7 @@ function _check(this::Check, ctxt::AnalysisContext, mod::SyntaxNode)::Nothing return nothing end -function _matches_module_name(mod_name::AbstractString, comment::AbstractString) +function _matches_module_name(mod_name::AbstractString, comment::AbstractString)::Bool return occursin(Regex("(module[ ]+)?" * mod_name), comment) end diff --git a/checks/ModuleExportLocation.jl b/checks/ModuleExportLocation.jl index 74302cc..14eed55 100644 --- a/checks/ModuleExportLocation.jl +++ b/checks/ModuleExportLocation.jl @@ -9,8 +9,9 @@ Analysis.id(::Check) = "module-export-location" Analysis.severity(::Check) = 9 Analysis.synopsis(::Check) = "Exports should be implemented after the include instructions" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) + return nothing end _no_ex_imports(node::SyntaxNode) = ! (is_import(node) || is_export(node)) diff --git a/checks/ModuleImportLocation.jl b/checks/ModuleImportLocation.jl index 0ff9fcf..3b75a03 100644 --- a/checks/ModuleImportLocation.jl +++ b/checks/ModuleImportLocation.jl @@ -11,8 +11,9 @@ Analysis.synopsis(::Check) = "Packages should be imported after the module keywo const USER_MSG = "Move imports to the top of the module, before any actual code" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) + return nothing end function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing diff --git a/checks/ModuleIncludeLocation.jl b/checks/ModuleIncludeLocation.jl index 7b7a2c3..ca0d24a 100644 --- a/checks/ModuleIncludeLocation.jl +++ b/checks/ModuleIncludeLocation.jl @@ -9,8 +9,9 @@ Analysis.id(::Check) = "module-include-location" Analysis.severity(::Check) = 9 Analysis.synopsis(::Check) = "The list of included files should be after the list of imported packages" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) + return nothing end function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing diff --git a/checks/ModuleSingleImportLine.jl b/checks/ModuleSingleImportLine.jl index eb5af67..f3dbb08 100644 --- a/checks/ModuleSingleImportLine.jl +++ b/checks/ModuleSingleImportLine.jl @@ -8,8 +8,9 @@ Analysis.id(::Check) = "module-single-import-line" Analysis.severity(::Check) = 9 Analysis.synopsis(::Check) = "The list of packages should be in alphabetical order" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) + return nothing end function _check(this::Check, ctxt::AnalysisContext, module_node::SyntaxNode)::Nothing diff --git a/checks/NestingOfConditionalStatements.jl b/checks/NestingOfConditionalStatements.jl index b3183ee..ac3c3a7 100644 --- a/checks/NestingOfConditionalStatements.jl +++ b/checks/NestingOfConditionalStatements.jl @@ -12,11 +12,12 @@ Analysis.synopsis(::Check) = "Avoid deep nesting of conditional statements" const MAX_ALLOWED_NESTING_LEVELS = 3 const USER_MSG = "This conditional expression is too deeply nested (deeper than $MAX_ALLOWED_NESTING_LEVELS levels)." -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_flow_cntrl, n -> _check(this, ctxt, n)) + return nothing end -function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing @assert is_flow_cntrl(node) "Expected a flow control node, got [$(kind(node))]." # Count the nesting level of conditional statements @@ -24,6 +25,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) length_of_keyword = length(string(kind(node))) report_violation(ctxt, this, node, USER_MSG; offsetspan=(0, length_of_keyword)) end + return nothing end function _conditional_nesting_level(node::SyntaxNode)::Int diff --git a/checks/NewlineAtFileEnd.jl b/checks/NewlineAtFileEnd.jl index 6c0035d..0dbbd67 100644 --- a/checks/NewlineAtFileEnd.jl +++ b/checks/NewlineAtFileEnd.jl @@ -11,7 +11,7 @@ Analysis.id(::Check) = "newline-at-file-end" Analysis.severity(::Check) = 7 Analysis.synopsis(::Check) = "Single newline at the end of file" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_toplevel, n -> _check(this, ctxt, n)) return nothing end diff --git a/checks/NoWhitespaceAroundTypeOperators.jl b/checks/NoWhitespaceAroundTypeOperators.jl index bd96c06..162d0b6 100644 --- a/checks/NoWhitespaceAroundTypeOperators.jl +++ b/checks/NoWhitespaceAroundTypeOperators.jl @@ -9,15 +9,16 @@ Analysis.id(::Check) = "no-whitespace-around-type-operators" Analysis.severity(::Check) = 7 Analysis.synopsis(::Check) = "Do not add whitespace around type operators" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, _is_type_assertion_or_constraint, n -> _check(this, ctxt, n)) + return nothing end -function _is_type_assertion_or_constraint(node) +function _is_type_assertion_or_constraint(node)::Bool return kind(node) in KSet":: <: >:" end -function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing code = node.source.code if is_prefix_op_call(node) start = node.position @@ -39,6 +40,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) "Omit whitespace around this operator" ) end + return nothing end end # module NoWhitespaceAroundTypeOperators diff --git a/checks/OmitTrailingWhiteSpace.jl b/checks/OmitTrailingWhiteSpace.jl index a1ce802..4ae1f08 100644 --- a/checks/OmitTrailingWhiteSpace.jl +++ b/checks/OmitTrailingWhiteSpace.jl @@ -9,11 +9,12 @@ Analysis.id(::Check) = "omit-trailing-white-space" Analysis.severity(::Check) = 7 Analysis.synopsis(::Check) = "Omit spaces at the end of a line" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_toplevel, n -> _check(this, ctxt, n)) + return nothing end -function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing code = node.source.code for m in eachmatch(r"( +)\r?\n", code) line::Int = count("\n", code[1:m.offset]) + 1 @@ -21,6 +22,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) bufferrange = m.offset:m.offset+length(m.captures[1]) report_violation(ctxt, this, (line,col), bufferrange, synopsis(this)) end + return nothing end end # module OmitTrailingWhiteSpace diff --git a/checks/PreferConstVariablesOverNonConstGlobalVariables.jl b/checks/PreferConstVariablesOverNonConstGlobalVariables.jl index 8b6127d..56f0ba0 100644 --- a/checks/PreferConstVariablesOverNonConstGlobalVariables.jl +++ b/checks/PreferConstVariablesOverNonConstGlobalVariables.jl @@ -10,7 +10,7 @@ Analysis.id(::Check) = "prefer-const-variables-over-non-const-global-variables" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Prefer const variables over non-const global variables" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> is_global_decl(n) && is_assignment(n), node -> begin ancs = ancestors(node) head = ancs[1:something(findfirst(is_scope_construct, ancs), length(ancs))] @@ -26,6 +26,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end end) + return nothing end end # module PreferConstVariablesOverNonConstGlobalVariables diff --git a/checks/PrefixOfAbstractTypeNames.jl b/checks/PrefixOfAbstractTypeNames.jl index 624befa..74e8715 100644 --- a/checks/PrefixOfAbstractTypeNames.jl +++ b/checks/PrefixOfAbstractTypeNames.jl @@ -8,11 +8,12 @@ Analysis.id(::Check) = "prefix-of-abstract-type-names" Analysis.severity(::Check) = 4 Analysis.synopsis(::Check) = "Abstract type names should be prefixed by \"Abstract\"." -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"abstract", node -> _check(this, ctxt, node)) + return nothing end -function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing @assert kind(node) == K"abstract" "Expected an [abstract] node, got $(kind(node))" type_id = find_lhs_of_kind(K"Identifier", node) @assert ! isnothing(type_id) "Got a type declaration without name (identifier)." @@ -22,6 +23,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode) "Abstract type names like $type_name should have prefix \"Abstract\"." ) end + return nothing end end # module PrefixOfAbstractTypeNames diff --git a/checks/ShortHandFunctionTooComplicated.jl b/checks/ShortHandFunctionTooComplicated.jl index a75413c..a474a49 100644 --- a/checks/ShortHandFunctionTooComplicated.jl +++ b/checks/ShortHandFunctionTooComplicated.jl @@ -9,16 +9,17 @@ Analysis.id(::Check) = "short-hand-function-too-complicated" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Short-hand notation with concise functions" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"function", func -> begin body = get_func_body(func) if !isnothing(body) && kind(body) != K"block" _check(this, ctxt, func, body) end end) + return nothing end -function _check(this::Check, ctxt::AnalysisContext, func::SyntaxNode, body::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, func::SyntaxNode, body::SyntaxNode)::Nothing report() = report_violation(ctxt, this, body, "Function '$(get_func_name(func))' is too complex for the shorthand notation; use keyword 'function'." ) diff --git a/checks/SingleModuleFile.jl b/checks/SingleModuleFile.jl index ce060a6..3f8e843 100644 --- a/checks/SingleModuleFile.jl +++ b/checks/SingleModuleFile.jl @@ -9,11 +9,12 @@ Analysis.id(::Check) = "single-module-file" Analysis.severity(::Check) = 5 Analysis.synopsis(::Check) = "Single module per file" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, node -> _check(this, ctxt, node)) + return nothing end -function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing @assert kind(modjule) == K"module" "Expected a [module] node, got [$(kind(node))]." father = modjule.parent kids = children(father) @@ -48,6 +49,7 @@ function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode) end end end + return nothing end end # module SingleModuleFile diff --git a/checks/SingleSpaceAfterCommasAndSemicolons.jl b/checks/SingleSpaceAfterCommasAndSemicolons.jl index c28b92a..19adfb2 100644 --- a/checks/SingleSpaceAfterCommasAndSemicolons.jl +++ b/checks/SingleSpaceAfterCommasAndSemicolons.jl @@ -10,10 +10,10 @@ Analysis.id(::Check) = "single-space-after-commas-and-semicolons" Analysis.severity(::Check) = 7 Analysis.synopsis(::Check) = "Commas and semicolons are followed, but not preceded, by a space." -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_toplevel, node -> begin code = node.source.code - report_if_space(pos::Integer, func::Function, shouldhave::Integer, msg::String) = begin + report_if_space(pos::Integer, func::Function, shouldhave::Integer, msg::String)::Nothing = begin range = func(code, pos) if length(range) != shouldhave && !contains(code[range], '\n') # skip check if range contains a newline report_violation(ctxt, this, @@ -22,6 +22,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) msg ) end + return nothing end for m in eachmatch(r"[;,]", code) # Find each occurrence in code pos = m.offset @@ -32,6 +33,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end end) + return nothing end function _find_whitespace_func(forward::Bool)::Function diff --git a/checks/StructMembersAreInLowerSnakeCase.jl b/checks/StructMembersAreInLowerSnakeCase.jl index 7216c29..d0398ff 100644 --- a/checks/StructMembersAreInLowerSnakeCase.jl +++ b/checks/StructMembersAreInLowerSnakeCase.jl @@ -9,15 +9,16 @@ Analysis.id(::Check) = "struct-members-are-in-lower-snake-case" Analysis.severity(::Check) = 8 Analysis.synopsis(::Check) = "Struct members should be in \"lower_snake_case\"." -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) === K"struct", node -> begin for field in get_struct_members(node) _check(this, ctxt, field) end end) + return nothing end -function _check(this::Check, ctxt::AnalysisContext, field::SyntaxNode) +function _check(this::Check, ctxt::AnalysisContext, field::SyntaxNode)::Nothing @assert kind(field.parent) == K"block" && kind(field.parent.parent) == K"struct" "Expected a node representing" * " a field (child of a [struct])" field.parent @@ -31,6 +32,7 @@ function _check(this::Check, ctxt::AnalysisContext, field::SyntaxNode) if !is_lower_snake(string(field_name)) report_violation(ctxt, this, field_name, "Field '$field_name' not in \"lower_snake_case\"") end + return nothing end end # module StructMembersAreInLowerSnakeCase diff --git a/checks/TooManyTypesInUnions.jl b/checks/TooManyTypesInUnions.jl index 34c96c0..76c94ed 100644 --- a/checks/TooManyTypesInUnions.jl +++ b/checks/TooManyTypesInUnions.jl @@ -10,7 +10,7 @@ Analysis.synopsis(::Check) = "Too many types in Unions" const MAX_UNION_TYPES = 4 -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_union_decl, node -> begin local union_types = children(node)[2:end] # discard the 1st, which is "Union" local count = length(union_types) @@ -18,6 +18,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) report_violation(ctxt, this, node, "Union has too many types ($count > $MAX_UNION_TYPES).") end end) + return nothing end end # module TooManyTypesInUnions diff --git a/checks/TypeNamesUpperCamelCase.jl b/checks/TypeNamesUpperCamelCase.jl index 9d449b4..eaceba6 100644 --- a/checks/TypeNamesUpperCamelCase.jl +++ b/checks/TypeNamesUpperCamelCase.jl @@ -9,7 +9,7 @@ Analysis.id(::Check) = "type-names-upper-camel-case" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Type names should be in \"UpperCamelCase\"" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) ∈ KSet"abstract struct", node -> begin identifier = find_lhs_of_kind(K"Identifier", node) if ! isnothing(identifier) @@ -19,6 +19,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end end) + return nothing end end # module TypeNamesUpperCamelCase diff --git a/checks/UseEachindexToIterateIndices.jl b/checks/UseEachindexToIterateIndices.jl index 1084668..c5ec419 100644 --- a/checks/UseEachindexToIterateIndices.jl +++ b/checks/UseEachindexToIterateIndices.jl @@ -11,8 +11,9 @@ Analysis.id(::Check) = "use-eachindex-to-iterate-indices" Analysis.severity(::Check) = 5 Analysis.synopsis(::Check) = "Use eachindex() instead of a constructed range for iteration over a collection." -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"for", node -> _check(this, ctxt, node)) + return nothing end function _check(this::Check, ctxt::AnalysisContext, for_node::SyntaxNode)::Nothing diff --git a/checks/UseIsinfToCheckForInfinite.jl b/checks/UseIsinfToCheckForInfinite.jl index 7816570..002e525 100644 --- a/checks/UseIsinfToCheckForInfinite.jl +++ b/checks/UseIsinfToCheckForInfinite.jl @@ -10,7 +10,7 @@ Analysis.id(::Check) = "use-isinf-to-check-for-infinite" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Use isinf to check for infinite values" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_eq_neq_comparison, node -> begin apply_to_operands(node, node -> begin if extract_special_value(node) ∈ SyntaxNodeHelpers.INF_VALUES @@ -18,6 +18,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end) end) + return nothing end end # module UseIsinfToCheckForInfinite diff --git a/checks/UseIsmissingToCheckForMissingValues.jl b/checks/UseIsmissingToCheckForMissingValues.jl index afcdaff..651c2ee 100644 --- a/checks/UseIsmissingToCheckForMissingValues.jl +++ b/checks/UseIsmissingToCheckForMissingValues.jl @@ -10,7 +10,7 @@ Analysis.id(::Check) = "use-ismissing-to-check-for-missing-values" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Use ismissing to check for missing values" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_eq_neq_comparison, node -> begin apply_to_operands(node, node -> begin if extract_special_value(node) ∈ SyntaxNodeHelpers.MISSING_VALUES @@ -18,6 +18,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end) end) + return nothing end end # module UseIsmissingToCheckForMissingValues diff --git a/checks/UseIsnanToCheckForNan.jl b/checks/UseIsnanToCheckForNan.jl index 2b0074d..0922794 100644 --- a/checks/UseIsnanToCheckForNan.jl +++ b/checks/UseIsnanToCheckForNan.jl @@ -10,7 +10,7 @@ Analysis.id(::Check) = "use-isnan-to-check-for-nan" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Use isnan to check for not-a-number values" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_eq_neq_comparison, node -> begin apply_to_operands(node, node -> begin if extract_special_value(node) ∈ SyntaxNodeHelpers.NAN_VALUES @@ -18,6 +18,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) end end) end) + return nothing end end # module UseIsnanToCheckForNan diff --git a/checks/UseSpacesInsteadOfTabs.jl b/checks/UseSpacesInsteadOfTabs.jl index e0a41cc..cd5ae36 100644 --- a/checks/UseSpacesInsteadOfTabs.jl +++ b/checks/UseSpacesInsteadOfTabs.jl @@ -9,7 +9,7 @@ Analysis.synopsis(::Check) = "Use spaces instead of tabs for indentation" const REGEX = r"(\s*)\t+.*" -function Analysis.init(this::Check, ctxt::AnalysisContext) +function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"toplevel", node -> begin code = node.source.code starts = node.source.line_starts @@ -27,6 +27,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext) linenr += 1 end end) + return nothing end end # module UseSpacesInsteadOfTabs diff --git a/src/Analysis.jl b/src/Analysis.jl index 0fc5c0a..5feff19 100644 --- a/src/Analysis.jl +++ b/src/Analysis.jl @@ -82,12 +82,12 @@ function _find_greenleaf(leaves::Vector{GreenLeaf}, pos::Int)::Union{GreenLeaf, return nothing end -function _get_green_leaves!(list::Vector{GreenLeaf}, sf::SourceFile, node::GreenNode, pos::Int) +function _get_green_leaves!(list::Vector{GreenLeaf}, sf::SourceFile, node::GreenNode, pos::Int)::Nothing cs = children(node) if isnothing(cs) rng = range(pos, prevind(sf.code, pos + node.span)) push!(list, GreenLeaf(sf, node, rng)) - return + return nothing end p = pos @@ -95,6 +95,7 @@ function _get_green_leaves!(list::Vector{GreenLeaf}, sf::SourceFile, node::Green _get_green_leaves!(list, sf, child, p) p += child.span end + return nothing end function _get_green_leaves(node::SyntaxNode)::Vector{GreenLeaf} @@ -240,7 +241,7 @@ function discover_checks()::Nothing end function _invoke_checks(ctxt::AnalysisContext, node::SyntaxNode)::Nothing - visitor = function(n::SyntaxNode) + visitor(n::SyntaxNode)::Nothing = begin for reg in ctxt.registrations if reg.predicate(n) #println("Invoking action for node type: ", reg.nodeType) @@ -249,6 +250,7 @@ function _invoke_checks(ctxt::AnalysisContext, node::SyntaxNode)::Nothing #println("Not a match: $(reg.nodeType) vs $(kind(n))") end end + return nothing end # TODO: Is the enter and exit on the main level really necessary? diff --git a/src/JuliaCheck.jl b/src/JuliaCheck.jl index e403228..c039ea7 100755 --- a/src/JuliaCheck.jl +++ b/src/JuliaCheck.jl @@ -64,7 +64,7 @@ function _parse_commandline(args::Vector{String}) return parse_args(args, s) end -function main(args::Vector{String}) +function main(args::Vector{String})::Nothing if isempty(args) _parse_commandline(["-h"]) return nothing @@ -113,6 +113,7 @@ function main(args::Vector{String}) end print_violations(violation_printer, output_file_arg, violations) println() + return nothing end _has_julia_ext(file_arg::String)::Bool = lowercase(splitext(file_arg)[end]) == ".jl" diff --git a/src/MutatingFunctionsHelpers.jl b/src/MutatingFunctionsHelpers.jl index cec7c98..788f685 100644 --- a/src/MutatingFunctionsHelpers.jl +++ b/src/MutatingFunctionsHelpers.jl @@ -17,7 +17,7 @@ assignments and call that mutate a variable. Currently, this list is: """ function get_mutated_variables_in_scope(ctxt::AnalysisContext, scope_node::SyntaxNode)::Set{String} all_mutated_variables = Set{String}() - visitor_func = function(n::SyntaxNode) + visitor_func(n::SyntaxNode)::Nothing = begin if is_array_assignment(n) mutated_var = string(first(children(n))) push!(all_mutated_variables, mutated_var) @@ -32,6 +32,7 @@ function get_mutated_variables_in_scope(ctxt::AnalysisContext, scope_node::Synta mutated_var = string(first(children(field_assignment))) push!(all_mutated_variables, mutated_var) end + return nothing end Analysis.dfs_traversal(ctxt, scope_node, visitor_func) return all_mutated_variables diff --git a/src/Properties.jl b/src/Properties.jl index 0259e83..1c01dda 100644 --- a/src/Properties.jl +++ b/src/Properties.jl @@ -225,11 +225,13 @@ function get_func_arguments(node::SyntaxNode)::Vector{SyntaxNode} # Probably a function "stub", which declares a function name but no methods. return [] end - # This returns all the arguments without any further processing. - # As such, this may contain: - # - only positional arguments (direct children) - # - only keyword arguments (grandchildren, children of a parameters node) - # - both (combination of children and parameters->grandchildren) + #= + This returns all the arguments without any further processing. + As such, this may contain: + - only positional arguments (direct children) + - only keyword arguments (grandchildren, children of a parameters node) + - both (combination of children and parameters->grandchildren) + =# return children(call)[2:end] # discard the function's name (1st identifier in this list) end diff --git a/src/SymbolTable.jl b/src/SymbolTable.jl index 5cb70a4..2899477 100644 --- a/src/SymbolTable.jl +++ b/src/SymbolTable.jl @@ -88,8 +88,9 @@ Module 'Main' is always there, at the bottom of the stack of modules. This function makes sure to reflect that situation. """ -function enter_main_module!(table::SymbolTableStruct) +function enter_main_module!(table::SymbolTableStruct)::Nothing _enter_module!(table, "Main") + return nothing end """ @@ -142,12 +143,14 @@ the file under analysis, and we will surely get confused about its scope. =# -function _enter_scope!(table::SymbolTableStruct) +function _enter_scope!(table::SymbolTableStruct)::Nothing push!(scopes_within_module(table), Scope()) + return nothing end -function _exit_scope!(table::SymbolTableStruct) +function _exit_scope!(table::SymbolTableStruct)::Nothing pop!(scopes_within_module(table)) + return nothing end _global_scope(table::SymbolTableStruct)::Scope = last(scopes_within_module(table)) @@ -191,13 +194,14 @@ Register an identifier. """ _declare!(table::SymbolTableStruct, symbol::SyntaxNode) = _declare_on_scope!(_current_scope(table), symbol, nothing) -function _declare_on_scope!(scp::Scope, node::SyntaxNode, type_spec::TypeSpecifier) +function _declare_on_scope!(scp::Scope, node::SyntaxNode, type_spec::TypeSpecifier)::Nothing symbol_id = _get_symbol_id(node) if haskey(scp, symbol_id) push!(scp[symbol_id].all_nodes, node) else scp[symbol_id] = SymbolTableItem(node, type_spec) end + return nothing end @@ -226,7 +230,7 @@ while preserving the state in between. Currently logs new modules, functions, and (global) variables. """ -function update_symbol_table_on_node_enter!(table::SymbolTableStruct, node::SyntaxNode) +function update_symbol_table_on_node_enter!(table::SymbolTableStruct, node::SyntaxNode)::Nothing if is_module(node) _enter_module!(table, node) elseif is_function(node) @@ -239,9 +243,10 @@ function update_symbol_table_on_node_enter!(table::SymbolTableStruct, node::Synt scope = is_assignment_to_global ? _global_scope(table) : _current_scope(table) _process_assignment!(scope, node) end + return nothing end -function _process_function!(table::SymbolTableStruct, node::SyntaxNode) +function _process_function!(table::SymbolTableStruct, node::SyntaxNode)::Nothing fname = get_func_name(node) if !isnothing(fname) if kind(fname) == K"Identifier" @@ -262,9 +267,10 @@ function _process_function!(table::SymbolTableStruct, node::SyntaxNode) _process_argument!(table, arg) end end + return nothing end -function _process_global!(table::SymbolTableStruct, node::SyntaxNode) +function _process_global!(table::SymbolTableStruct, node::SyntaxNode)::Nothing # Handle statements like `global x, y = 1, 2` # We need to handle assignment here and cannot wait until the descendant 'assignment' expression # is encountered in `update_symbol_table_on_node_enter!`, because the check might listen to `is_global_decl` event. @@ -279,17 +285,18 @@ function _process_global!(table::SymbolTableStruct, node::SyntaxNode) _declare_global!(table, c) end end + return nothing end -function _process_argument!(table::SymbolTableStruct, node::SyntaxNode) +function _process_argument!(table::SymbolTableStruct, node::SyntaxNode)::Nothing arg = find_lhs_of_kind(K"Identifier", node) - if isnothing(arg) - return nothing + if ! isnothing(arg) + _declare!(table, arg) end - _declare!(table, arg) + return nothing end -function _process_assignment!(scope::Scope, node::SyntaxNode) +function _process_assignment!(scope::Scope, node::SyntaxNode)::Nothing @assert kind(node) == K"=" "Expected a [=] node, got [$(kind(node))]." assignees = get_all_assignees(node) if length(assignees) == 1 @@ -303,10 +310,12 @@ function _process_assignment!(scope::Scope, node::SyntaxNode) _declare_on_scope!(scope, var_node, nothing) end end + return nothing end -function _process_struct!(table::SymbolTableStruct, node::SyntaxNode) +function _process_struct!(table::SymbolTableStruct, node::SyntaxNode)::Nothing _declare!(table, find_lhs_of_kind(K"Identifier", node)) + return nothing end """ @@ -316,12 +325,13 @@ When a module or a scope-opening function is left, this is then used to exit scopes and move back to the table below it (so scoped variables within the current scope are no longer present then). """ -function update_symbol_table_on_node_leave!(table::SymbolTableStruct, node::SyntaxNode) +function update_symbol_table_on_node_leave!(table::SymbolTableStruct, node::SyntaxNode)::Nothing if is_module(node) exit_module!(table) elseif opens_scope(node) _exit_scope!(table) end + return nothing end function get_initial_type_of_node(table::SymbolTableStruct, assignment_node::SyntaxNode)::TypeSpecifier From e3ccf38a90149d6bdf2cad4df70a0a93bc8bdb39 Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Fri, 23 Jan 2026 11:26:26 +0100 Subject: [PATCH 03/10] Fixed some violations on `document-constants` --- checks/DoNotCommentOutCode.jl | 5 ++++- ...ationMarkInFunctionIdentifierIfMutating.jl | 8 ++++++-- .../FunctionsMutateOnlyZeroOrOneArguments.jl | 3 +-- checks/ModuleImportLocation.jl | 6 +++--- checks/NestingOfConditionalStatements.jl | 7 +++++-- checks/ShortHandFunctionTooComplicated.jl | 9 ++++++--- checks/SpaceAroundBinaryInfixOperators.jl | 4 +++- checks/UnderscorePrefixForPrivateFunctions.jl | 4 +++- checks/UseEachindexToIterateIndices.jl | 4 +++- src/Analysis.jl | 19 ++++++++++++------- src/JuliaCheck.jl | 4 +++- 11 files changed, 49 insertions(+), 24 deletions(-) diff --git a/checks/DoNotCommentOutCode.jl b/checks/DoNotCommentOutCode.jl index e8d4e0a..840c805 100644 --- a/checks/DoNotCommentOutCode.jl +++ b/checks/DoNotCommentOutCode.jl @@ -44,7 +44,10 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing return nothing end -_report(ctxt::AnalysisContext, this::Check, range::UnitRange) = report_violation(ctxt, this, range, "Comment contains code") +function _report(ctxt::AnalysisContext, this::Check, range::UnitRange)::Nothing + report_violation(ctxt, this, range, "Comment contains code") + return nothing +end # If JS can parse the comment contents, it must be code function _contains_code(text::AbstractString)::Bool diff --git a/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl b/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl index 6e09a5e..31ef1b1 100644 --- a/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl +++ b/checks/ExclamationMarkInFunctionIdentifierIfMutating.jl @@ -9,14 +9,18 @@ struct Check<:Analysis.Check end Analysis.id(::Check) = "exclamation-mark-in-function-identifier-if-mutating" Analysis.severity(::Check) = 4 -Analysis.synopsis(::Check) = "Only functions postfixed with an exclamation mark can mutate an argument." +function Analysis.synopsis(::Check)::String + return "Only functions postfixed with an exclamation mark can mutate an argument." +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, _is_nonmutating_fn, n -> _check_function(this, ctxt, n)) return nothing end -_is_nonmutating_fn(n::SyntaxNode)::Bool = is_function(n) && !endswith(string(get_func_name(n)), "!") +function _is_nonmutating_fn(n::SyntaxNode)::Bool + return is_function(n) && !endswith(string(get_func_name(n)), "!") +end function _check_function(this::Check, ctxt::AnalysisContext, function_node::SyntaxNode)::Nothing func_arg_strings = get_string_fn_args(function_node) diff --git a/checks/FunctionsMutateOnlyZeroOrOneArguments.jl b/checks/FunctionsMutateOnlyZeroOrOneArguments.jl index 4a86eb4..29e2594 100644 --- a/checks/FunctionsMutateOnlyZeroOrOneArguments.jl +++ b/checks/FunctionsMutateOnlyZeroOrOneArguments.jl @@ -5,8 +5,7 @@ using ...MutatingFunctionsHelpers: get_mutated_variables_in_scope using ...Properties: get_flattened_fn_arg_nodes, get_func_body, get_string_arg, is_function include("_common.jl") -struct Check <: Analysis.Check end - +struct Check<:Analysis.Check end Analysis.id(::Check) = "functions-mutate-only-zero-or-one-arguments" Analysis.severity(::Check) = 3 Analysis.synopsis(::Check) = "Functions should change only one or zero argument(s)." diff --git a/checks/ModuleImportLocation.jl b/checks/ModuleImportLocation.jl index 3b75a03..7bb00ca 100644 --- a/checks/ModuleImportLocation.jl +++ b/checks/ModuleImportLocation.jl @@ -9,8 +9,6 @@ Analysis.id(::Check) = "module-import-location" Analysis.severity(::Check) = 9 Analysis.synopsis(::Check) = "Packages should be imported after the module keyword." -const USER_MSG = "Move imports to the top of the module, before any actual code" - function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) return nothing @@ -28,7 +26,9 @@ function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothin if is_import(node) && !is_include(node) # We can skip include's because they are followed by an import # or a using (we made sure in 'is_import'). - report_violation(ctxt, this, node, USER_MSG) + report_violation(ctxt, this, node, + "Move imports to the top of the module, before any actual code" + ) end end end diff --git a/checks/NestingOfConditionalStatements.jl b/checks/NestingOfConditionalStatements.jl index ac3c3a7..040fbe2 100644 --- a/checks/NestingOfConditionalStatements.jl +++ b/checks/NestingOfConditionalStatements.jl @@ -9,8 +9,8 @@ Analysis.id(::Check) = "nesting-of-conditional-statements" Analysis.severity(::Check) = 4 Analysis.synopsis(::Check) = "Avoid deep nesting of conditional statements" +""" Violation is produced when nesting exceeds this threshold """ const MAX_ALLOWED_NESTING_LEVELS = 3 -const USER_MSG = "This conditional expression is too deeply nested (deeper than $MAX_ALLOWED_NESTING_LEVELS levels)." function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_flow_cntrl, n -> _check(this, ctxt, n)) @@ -23,7 +23,10 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing # Count the nesting level of conditional statements if _conditional_nesting_level(node) > MAX_ALLOWED_NESTING_LEVELS length_of_keyword = length(string(kind(node))) - report_violation(ctxt, this, node, USER_MSG; offsetspan=(0, length_of_keyword)) + report_violation(ctxt, this, node, + "This conditional expression is too deeply nested (deeper than $MAX_ALLOWED_NESTING_LEVELS levels)." + ; offsetspan=(0, length_of_keyword) + ) end return nothing end diff --git a/checks/ShortHandFunctionTooComplicated.jl b/checks/ShortHandFunctionTooComplicated.jl index a474a49..17dc914 100644 --- a/checks/ShortHandFunctionTooComplicated.jl +++ b/checks/ShortHandFunctionTooComplicated.jl @@ -20,9 +20,12 @@ function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing end function _check(this::Check, ctxt::AnalysisContext, func::SyntaxNode, body::SyntaxNode)::Nothing - report() = report_violation(ctxt, this, body, - "Function '$(get_func_name(func))' is too complex for the shorthand notation; use keyword 'function'." - ) + function report()::Nothing + report_violation(ctxt, this, body, + "Function '$(get_func_name(func))' is too complex for the shorthand notation; use keyword 'function'." + ) + return nothing + end line_len = length(sourcetext(func)) if line_len > MAX_LINE_LENGTH diff --git a/checks/SpaceAroundBinaryInfixOperators.jl b/checks/SpaceAroundBinaryInfixOperators.jl index 80d6720..8a992b1 100644 --- a/checks/SpaceAroundBinaryInfixOperators.jl +++ b/checks/SpaceAroundBinaryInfixOperators.jl @@ -20,7 +20,9 @@ include("_common.jl") struct Check<:Analysis.Check end Analysis.id(::Check) = "space-around-binary-infix-operators" Analysis.severity(::Check) = 7 -Analysis.synopsis(::Check) = "Selected binary infix operators and the = character are followed and preceded by a single space." +function Analysis.synopsis(::Check)::String + return "Selected binary infix operators and the = character are followed and preceded by a single space." +end """ Kinds for which the rule will assert no whitespace surrounds them diff --git a/checks/UnderscorePrefixForPrivateFunctions.jl b/checks/UnderscorePrefixForPrivateFunctions.jl index b5ed7fd..694968f 100644 --- a/checks/UnderscorePrefixForPrivateFunctions.jl +++ b/checks/UnderscorePrefixForPrivateFunctions.jl @@ -7,7 +7,9 @@ using ...Properties: get_func_name, is_export, is_function, is_module struct Check<:Analysis.Check end Analysis.id(::Check) = "underscore-prefix-for-private-functions" Analysis.severity(::Check) = 8 -Analysis.synopsis(::Check) = "Private functions are prefixed with one underscore _ character." +function Analysis.synopsis(::Check)::String + return "Private functions are prefixed with one underscore _ character." +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) diff --git a/checks/UseEachindexToIterateIndices.jl b/checks/UseEachindexToIterateIndices.jl index c5ec419..b059852 100644 --- a/checks/UseEachindexToIterateIndices.jl +++ b/checks/UseEachindexToIterateIndices.jl @@ -9,7 +9,9 @@ using ...SyntaxNodeHelpers: find_descendants struct Check<:Analysis.Check end Analysis.id(::Check) = "use-eachindex-to-iterate-indices" Analysis.severity(::Check) = 5 -Analysis.synopsis(::Check) = "Use eachindex() instead of a constructed range for iteration over a collection." +function Analysis.synopsis(::Check)::String + return "Use eachindex() instead of a constructed range for iteration over a collection." +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"for", node -> _check(this, ctxt, node)) diff --git a/src/Analysis.jl b/src/Analysis.jl index 5feff19..6360ea8 100644 --- a/src/Analysis.jl +++ b/src/Analysis.jl @@ -42,6 +42,9 @@ sourcetext(gl::GreenLeaf)::String = gl.sourcefile.code[gl.range] kind(gl::GreenLeaf) = kind(gl.node) source_location(gl::GreenLeaf) = source_location(gl.sourcefile, gl.range.start) +const NullableGreenLeaf = Union{GreenLeaf, Nothing} +const NullableSyntaxNode = Union{SyntaxNode, Nothing} + struct CheckRegistration predicate::Function # A predicate function that determines if the action applies to a SyntaxNode action::Function # The action to be performed on SyntaxNode when the predicate applies @@ -54,16 +57,18 @@ struct AnalysisContext violations::Vector{Violation} symboltable::SymbolTableStruct - AnalysisContext(node::SyntaxNode, greenLeaves::Vector{GreenLeaf}) = new(node, greenLeaves, CheckRegistration[], Violation[], SymbolTableStruct()) + function AnalysisContext(node::SyntaxNode, greenLeaves::Vector{GreenLeaf}) + return new(node, greenLeaves, CheckRegistration[], Violation[], SymbolTableStruct()) + end end "Finds GreenLeaf containing given position." -function find_greenleaf(ctxt::AnalysisContext, pos::Int)::Union{GreenLeaf, Nothing} +function find_greenleaf(ctxt::AnalysisContext, pos::Int)::NullableGreenLeaf return _find_greenleaf(ctxt.greenleaves, pos) end "Performs a binary search to find the GreenLeaf containing given position." -function _find_greenleaf(leaves::Vector{GreenLeaf}, pos::Int)::Union{GreenLeaf, Nothing} +function _find_greenleaf(leaves::Vector{GreenLeaf}, pos::Int)::NullableGreenLeaf low = 1 high = length(leaves) while low <= high @@ -105,11 +110,11 @@ function _get_green_leaves(node::SyntaxNode)::Vector{GreenLeaf} end """ - find_syntaxnode_at_position(node::SyntaxNode, pos::Integer)::Union{SyntaxNode, Nothing} + find_syntaxnode_at_position(node::SyntaxNode, pos::Integer)::NullableSyntaxNode Finds the most specific SyntaxNode that spans the given character position `pos`. """ -function find_syntaxnode_at_position(node::SyntaxNode, pos::Integer)::Union{SyntaxNode, Nothing} +function find_syntaxnode_at_position(node::SyntaxNode, pos::Integer)::NullableSyntaxNode # Check if the current node's range contains the position. if ! (pos in JuliaSyntax.byte_range(node)) return nothing @@ -128,11 +133,11 @@ function find_syntaxnode_at_position(node::SyntaxNode, pos::Integer)::Union{Synt end """ - find_syntaxnode_at_position(ctxt::AnalysisContext, pos::Integer)::Union{SyntaxNode, Nothing} + find_syntaxnode_at_position(ctxt::AnalysisContext, pos::Integer)::NullableSyntaxNode Finds the most specific SyntaxNode that spans the given character position `pos`. """ -function find_syntaxnode_at_position(ctxt::AnalysisContext, pos::Integer)::Union{SyntaxNode, Nothing} +function find_syntaxnode_at_position(ctxt::AnalysisContext, pos::Integer)::NullableSyntaxNode return find_syntaxnode_at_position(ctxt.rootNode, pos) end diff --git a/src/JuliaCheck.jl b/src/JuliaCheck.jl index c039ea7..6a9982d 100755 --- a/src/JuliaCheck.jl +++ b/src/JuliaCheck.jl @@ -116,7 +116,9 @@ function main(args::Vector{String})::Nothing return nothing end -_has_julia_ext(file_arg::String)::Bool = lowercase(splitext(file_arg)[end]) == ".jl" +function _has_julia_ext(file_arg::String)::Bool + return lowercase(splitext(file_arg)[end]) == ".jl" +end function _get_files_to_analyze(file_arg::Vector{String})::Vector{String} file_set = [] From 9da5972d9679df7796769e647522e19b4f297e15 Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Fri, 23 Jan 2026 15:39:08 +0100 Subject: [PATCH 04/10] Fix violations for `indentation-levels-are-four-spaces` --- checks/DoNotChangeGeneratedIndices.jl | 4 ++-- checks/VariablesHaveFixedTypes.jl | 3 ++- src/Output.jl | 2 +- src/Properties.jl | 12 ++++++------ src/SymbolTable.jl | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/checks/DoNotChangeGeneratedIndices.jl b/checks/DoNotChangeGeneratedIndices.jl index c4050c7..ddf630f 100644 --- a/checks/DoNotChangeGeneratedIndices.jl +++ b/checks/DoNotChangeGeneratedIndices.jl @@ -1,7 +1,7 @@ module DoNotChangeGeneratedIndices using ...Properties: first_child, get_assignee, get_iteration_parts, - is_assignment, is_flow_cntrl, is_range + is_assignment, is_flow_cntrl, is_range include("_common.jl") @@ -25,7 +25,7 @@ function _check_for_loop(this::Check, ctxt::AnalysisContext, for_loop::SyntaxNod kind(iter_expr) == K"call" && kind(first_child(iter_expr)) == K"Identifier" && string(first_child(iter_expr)) ∈ ["eachindex", "enumerate", "axes"] - ) + ) # Look into the loop's body to see if `loop_var` is modified. @assert numchildren(for_loop) == 2 && kind(children(for_loop)[2]) == K"block" "An empty loop or what? $for_loop" diff --git a/checks/VariablesHaveFixedTypes.jl b/checks/VariablesHaveFixedTypes.jl index 6b82d0e..c12e4df 100644 --- a/checks/VariablesHaveFixedTypes.jl +++ b/checks/VariablesHaveFixedTypes.jl @@ -39,7 +39,8 @@ function _check(this::Check, ctxt::AnalysisContext, assignment_node::SyntaxNode) initial_type = get_initial_type_of_node(ctxt.symboltable, assignment_node) current_type = get_variable_type_from_node(assignment_node) report_violation(ctxt, this, assignment_node, - "Variable '$assigned_variable' has changed type (from $initial_type to $current_type).") + "Variable '$assigned_variable' has changed type (from $initial_type to $current_type)." + ) end return nothing end diff --git a/src/Output.jl b/src/Output.jl index 008bcc5..ae76e9e 100644 --- a/src/Output.jl +++ b/src/Output.jl @@ -1,7 +1,7 @@ module Output export ViolationPrinter, get_available_printers, shorthand, requiresfile, print_violations, - select_violation_printer, parse_output_file_arg + select_violation_printer, parse_output_file_arg import ..Analysis: Violation import InteractiveUtils: subtypes diff --git a/src/Properties.jl b/src/Properties.jl index 1c01dda..5836352 100644 --- a/src/Properties.jl +++ b/src/Properties.jl @@ -108,7 +108,7 @@ function is_global_decl(node::AnyTree)::Bool end function is_constant(node::AnyTree)::Bool return kind(node) == K"const" || - (kind(node) == K"global" && haschildren(node) && + (kind(node) == K"global" && haschildren(node) && kind(children(node)[1]) == K"const") end @@ -146,7 +146,7 @@ function is_range(node::SyntaxNode)::Bool (kind(kids[1]) == K"Identifier" && string(kids[1]) == "range") || (kind(kids[2]) == K"Identifier" && string(kids[2]) == ":") - ) + ) end return false end @@ -171,7 +171,7 @@ end function opens_scope(node::SyntaxNode) return is_function(node) || - kind(node) ∈ [KSet"for while try do let macro generator"] + kind(node) ∈ [KSet"for while try do let macro generator"] # comprehensions contain a generator end function closes_scope(node::SyntaxNode) @@ -315,7 +315,7 @@ function get_imported_pkg(node::SyntaxNode)::String else pkg = children(node)[1] if kind(pkg) == K":" || # importing/using items from a package - kind(pkg) == K"as" # import with an alias + kind(pkg) == K"as" # import with an alias pkg = children(pkg)[1] end @assert kind(pkg) == K"importpath" @@ -500,8 +500,8 @@ returned parts are `nothing` (but it is still a pair). function get_iteration_parts(for_loop::SyntaxNode)::Tuple{NullableNode, NullableNode} if kind(for_loop) == K"for" if !(haschildren(for_loop) && - kind(first_child(for_loop)) == K"iteration" - ) + kind(first_child(for_loop)) == K"iteration" + ) @debug "for loop does not have an [iteration]" for_loop return nothing, nothing end diff --git a/src/SymbolTable.jl b/src/SymbolTable.jl index 2899477..c4429c6 100644 --- a/src/SymbolTable.jl +++ b/src/SymbolTable.jl @@ -33,8 +33,8 @@ top to bottom. Symbols from other modules have to be qualified, or entered into the current module's global scope with a `using` declaration. =# -Scope = Dict{String, SymbolTableItem} -NestedScopes = Stack{Scope} +const Scope = Dict{String, SymbolTableItem} +const NestedScopes = Stack{Scope} """ A module containing an identifier and a stack of scopes. From ff551e26b0925bf398cd600509d947d5c02c5621 Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Fri, 23 Jan 2026 15:57:08 +0100 Subject: [PATCH 05/10] Fix violations for `avoid-extraneous-whitespace-between-open-and-close-character` --- src/Analysis.jl | 4 ++-- src/JuliaCheck.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Analysis.jl b/src/Analysis.jl index 6360ea8..9b81a8b 100644 --- a/src/Analysis.jl +++ b/src/Analysis.jl @@ -266,8 +266,8 @@ function _invoke_checks(ctxt::AnalysisContext, node::SyntaxNode)::Nothing end function run_analysis(sourcefile::SourceFile, checks::Vector{Check}; - print_ast::Bool = false, - print_llt::Bool = false, + print_ast::Bool=false, + print_llt::Bool=false, )::Vector{Violation} if length(checks) >= 1 diff --git a/src/JuliaCheck.jl b/src/JuliaCheck.jl index 6a9982d..e0111c8 100755 --- a/src/JuliaCheck.jl +++ b/src/JuliaCheck.jl @@ -106,8 +106,8 @@ function main(args::Vector{String})::Nothing fresh_checks::Vector{Check} = map(type -> typeof(type)(), checks_to_run) new_violations = Analysis.run_analysis(sourcefile, fresh_checks; - print_ast = arguments["ast"], - print_llt = arguments["llt"]) + print_ast=arguments["ast"], + print_llt=arguments["llt"]) append!(violations, new_violations) end end From f0c2ee83c910ec00791a3352b2fc8d185d66f7a5 Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Fri, 23 Jan 2026 16:07:27 +0100 Subject: [PATCH 06/10] Fix violations for `one-expression-per-line` --- src/JuliaCheck.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/JuliaCheck.jl b/src/JuliaCheck.jl index e0111c8..ed94371 100755 --- a/src/JuliaCheck.jl +++ b/src/JuliaCheck.jl @@ -4,15 +4,15 @@ using JuliaSyntax: first_byte, last_byte, SourceFile using ArgParse: ArgParseSettings, project_version, @add_arg_table!, parse_args using InteractiveUtils -include("Properties.jl"); import .Properties -include("TypeHelpers.jl"); import .TypeHelpers +include("Properties.jl") +include("TypeHelpers.jl") include("SyntaxNodeHelpers.jl") include("SymbolTable.jl") include("Analysis.jl") include("Output.jl") include("MutatingFunctionsHelpers.jl") -include("WhitespaceHelpers.jl"); import .WhitespaceHelpers -include("CommentHelpers.jl"); import .CommentHelpers +include("WhitespaceHelpers.jl") +include("CommentHelpers.jl") using .Analysis using .Output From 85247e281e3bfc5e0a8dc914dc188b24a3f963c8 Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Mon, 26 Jan 2026 13:09:25 +0100 Subject: [PATCH 07/10] Solve violations for `avoid-creating-empty-arrays-and-vectors` --- checks/AvoidGlobalVariables.jl | 2 +- checks/DoNotCommentOutCode.jl | 6 +++--- checks/DoNotNestMultilineComments.jl | 2 +- checks/IndentationLevelsAreFourSpaces.jl | 2 +- checks/ModuleEndComment.jl | 2 +- checks/ModuleExportLocation.jl | 2 +- checks/ModuleImportLocation.jl | 6 ++++-- checks/ModuleIncludeLocation.jl | 2 +- checks/NoWhitespaceAroundTypeOperators.jl | 2 +- checks/OmitTrailingWhiteSpace.jl | 2 +- src/CommentHelpers.jl | 6 +++--- src/JuliaCheck.jl | 2 +- src/Properties.jl | 4 ++-- src/SyntaxNodeHelpers.jl | 2 +- 14 files changed, 22 insertions(+), 20 deletions(-) diff --git a/checks/AvoidGlobalVariables.jl b/checks/AvoidGlobalVariables.jl index 981c7ab..2455d0e 100644 --- a/checks/AvoidGlobalVariables.jl +++ b/checks/AvoidGlobalVariables.jl @@ -1,8 +1,8 @@ module AvoidGlobalVariables using ...Properties: is_global_decl, is_constant, find_lhs_of_kind -using ...SyntaxNodeHelpers using ...SymbolTable +using ...SyntaxNodeHelpers include("_common.jl") diff --git a/checks/DoNotCommentOutCode.jl b/checks/DoNotCommentOutCode.jl index 840c805..8f6e120 100644 --- a/checks/DoNotCommentOutCode.jl +++ b/checks/DoNotCommentOutCode.jl @@ -2,7 +2,7 @@ module DoNotCommentOutCode using ...CommentHelpers: Comment, CommentBlock, get_comment_blocks, get_range, get_text, contains_comments using ...WhitespaceHelpers: combine_ranges -using JuliaSyntax: kind, @K_str, source_location, JuliaSyntax as JS +using JuliaSyntax: kind, @K_str, parseall, source_location include("_common.jl") @@ -49,11 +49,11 @@ function _report(ctxt::AnalysisContext, this::Check, range::UnitRange)::Nothing return nothing end -# If JS can parse the comment contents, it must be code +# If JuliaSyntax can parse the comment contents, it must be code function _contains_code(text::AbstractString)::Bool if !any(occursin(text), KEYWORDS) return false end try - JS.parseall(SyntaxNode, text) + parseall(SyntaxNode, text) catch return false end diff --git a/checks/DoNotNestMultilineComments.jl b/checks/DoNotNestMultilineComments.jl index 6bd24e4..fb5a78b 100644 --- a/checks/DoNotNestMultilineComments.jl +++ b/checks/DoNotNestMultilineComments.jl @@ -22,7 +22,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing # Search for next comment inside comment found::Union{UnitRange{Int}, Nothing} = findnext(ML_COMMENT, text, length(ML_COMMENT)) if !isnothing(found) - found = (comment.range.start-1) .+ found + found = (comment.range.start - 1) .+ found report_violation(ctxt, this, source_location(node.source, found.start), found, diff --git a/checks/IndentationLevelsAreFourSpaces.jl b/checks/IndentationLevelsAreFourSpaces.jl index 54ecc9f..6d17d40 100644 --- a/checks/IndentationLevelsAreFourSpaces.jl +++ b/checks/IndentationLevelsAreFourSpaces.jl @@ -26,7 +26,7 @@ function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing # their presence here, counting 4-1 extra spaces for each tab. indentation::Int = length(indenttext) + 3 * count(r"\t", indenttext) if rem(indentation, 4) > 0 - rng = range(gl.range.stop - length(indenttext) + 1, length=length(indenttext)) + rng = range(gl.range.stop - length(indenttext) + 1; length=length(indenttext)) pos = source_location(n.source, rng.start) report_violation(ctxt, this, pos, rng, synopsis(this)) end diff --git a/checks/ModuleEndComment.jl b/checks/ModuleEndComment.jl index cdbd582..8305066 100644 --- a/checks/ModuleEndComment.jl +++ b/checks/ModuleEndComment.jl @@ -22,7 +22,7 @@ function _check(this::Check, ctxt::AnalysisContext, mod::SyntaxNode)::Nothing comment_start = something(findnext('#', code, mod_end), length(code)) if comment_start >= eol filepos = source_location(mod.source, mod_end) - report_violation(ctxt, this, filepos, range(mod_end-2, length=3), "Missing end module comment") + report_violation(ctxt, this, filepos, range(mod_end - 2; length=3), "Missing end module comment") else comment_range = comment_start:eol comment = code[comment_range] diff --git a/checks/ModuleExportLocation.jl b/checks/ModuleExportLocation.jl index 14eed55..97f44da 100644 --- a/checks/ModuleExportLocation.jl +++ b/checks/ModuleExportLocation.jl @@ -18,7 +18,7 @@ _no_ex_imports(node::SyntaxNode) = ! (is_import(node) || is_export(node)) function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing @assert kind(modjule) == K"module" "Expected a [module] node, got [$(kind(modjule))]." - @assert numchildren(modjule) == 2 "This module has a weird shape: "* string(modjule) + @assert numchildren(modjule) == 2 "This module has a weird shape: " * string(modjule) @assert kind(children(modjule)[2]) == K"block" "The second child of a [module] node is not a [block]!" mod_body = children(children(modjule)[2]) diff --git a/checks/ModuleImportLocation.jl b/checks/ModuleImportLocation.jl index 7bb00ca..9516817 100644 --- a/checks/ModuleImportLocation.jl +++ b/checks/ModuleImportLocation.jl @@ -7,7 +7,9 @@ using ...Properties: is_import, is_include, is_module struct Check<:Analysis.Check end Analysis.id(::Check) = "module-import-location" Analysis.severity(::Check) = 9 -Analysis.synopsis(::Check) = "Packages should be imported after the module keyword." +function Analysis.synopsis(::Check) + return "Packages should be imported after the module keyword." +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) @@ -16,7 +18,7 @@ end function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing @assert kind(modjule) == K"module" "Expected a [module] node, got [$(kind(node))]." - @assert numchildren(modjule) == 2 "This module has a weird shape: "* string(modjule) + @assert numchildren(modjule) == 2 "This module has a weird shape: " * string(modjule) @assert kind(children(modjule)[2]) == K"block" "The second child of a [module] node is not a [block]!" mod_body = children(children(modjule)[2]) diff --git a/checks/ModuleIncludeLocation.jl b/checks/ModuleIncludeLocation.jl index ca0d24a..a349800 100644 --- a/checks/ModuleIncludeLocation.jl +++ b/checks/ModuleIncludeLocation.jl @@ -16,7 +16,7 @@ end function _check(this::Check, ctxt::AnalysisContext, modjule::SyntaxNode)::Nothing @assert kind(modjule) == K"module" "Expected a [module] node, got [$(kind(modjule))]." - @assert numchildren(modjule) == 2 "This module has a weird shape: "* string(modjule) + @assert numchildren(modjule) == 2 "This module has a weird shape: " * string(modjule) @assert kind(children(modjule)[2]) == K"block" "The second child of a [module] node is not a [block]!" mod_body = children(children(modjule)[2]) diff --git a/checks/NoWhitespaceAroundTypeOperators.jl b/checks/NoWhitespaceAroundTypeOperators.jl index 162d0b6..a2c3731 100644 --- a/checks/NoWhitespaceAroundTypeOperators.jl +++ b/checks/NoWhitespaceAroundTypeOperators.jl @@ -36,7 +36,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing linepos = source_location(node.source, start) report_violation(ctxt, this, linepos, - range(start, length=length(text_between)), + range(start; length=length(text_between)), "Omit whitespace around this operator" ) end diff --git a/checks/OmitTrailingWhiteSpace.jl b/checks/OmitTrailingWhiteSpace.jl index 4ae1f08..ea6766a 100644 --- a/checks/OmitTrailingWhiteSpace.jl +++ b/checks/OmitTrailingWhiteSpace.jl @@ -19,7 +19,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing for m in eachmatch(r"( +)\r?\n", code) line::Int = count("\n", code[1:m.offset]) + 1 col::Int = m.offset - something(findprev('\n', code, m.offset), 1) + 1 - bufferrange = m.offset:m.offset+length(m.captures[1]) + bufferrange = m.offset:m.offset + length(m.captures[1]) report_violation(ctxt, this, (line,col), bufferrange, synopsis(this)) end return nothing diff --git a/src/CommentHelpers.jl b/src/CommentHelpers.jl index add35d7..0cd7c14 100644 --- a/src/CommentHelpers.jl +++ b/src/CommentHelpers.jl @@ -23,7 +23,7 @@ function contains_comments(sn::SyntaxNode)::Bool end function get_comments(sn::SyntaxNode)::Vector{Comment} - comments = [] + comments = Vector{Comment}() for (i, ch) in enumerate(sn.raw.children) if kind(ch) == K"Comment" range = normalized_green_child_range(sn, i) @@ -38,9 +38,9 @@ Get the range and text representation of the direct children that are comment no Subsequent single-line comments are merged. Only sibling comments can ever belong to the same block. """ function get_comment_blocks(sn::SyntaxNode)::Vector{CommentBlock} - blocks = [] + blocks = Vector{CommentBlock}() green_children = sn.raw.children - curblock = [] + curblock = Vector{Comment}() # Iterate through green children, combining consecutive comment siblings into blocks # if there is only whitespace between them for (i, ch) in enumerate(green_children) diff --git a/src/JuliaCheck.jl b/src/JuliaCheck.jl index ed94371..59b0293 100755 --- a/src/JuliaCheck.jl +++ b/src/JuliaCheck.jl @@ -121,7 +121,7 @@ function _has_julia_ext(file_arg::String)::Bool end function _get_files_to_analyze(file_arg::Vector{String})::Vector{String} - file_set = [] + file_set = Vector{String}() for element in file_arg if isfile(element) && _has_julia_ext(element) push!(file_set, element) diff --git a/src/Properties.jl b/src/Properties.jl index 5836352..9418c91 100644 --- a/src/Properties.jl +++ b/src/Properties.jl @@ -122,7 +122,7 @@ end function is_operator(node::AnyTree)::Bool return JS.is_prefix_op_call(node) || - is_infix_operator(node) || + is_infix_operator(node) || JS.is_postfix_op_call(node) end function is_infix_operator(node::AnyTree)::Bool @@ -372,7 +372,7 @@ of whether there are still named arguments in there. """ function get_flattened_fn_arg_nodes(function_node::SyntaxNode)::Vector{SyntaxNode} func_arguments = get_func_arguments(function_node) - func_arg_nodes = [] + func_arg_nodes = Vector{SyntaxNode}() for arg in func_arguments # Parameters signifies keyword (also known as named) arguments. # All named arguments are then reported in subnodes. For now, we don't diff --git a/src/SyntaxNodeHelpers.jl b/src/SyntaxNodeHelpers.jl index 7fe95bc..c32dda6 100644 --- a/src/SyntaxNodeHelpers.jl +++ b/src/SyntaxNodeHelpers.jl @@ -85,7 +85,7 @@ Return a list of all descendant nodes of the given node that match the predicate By default visits the full tree. Use `stop_traversal=true` to stop recursing into subtree when a node matches predicate. """ function find_descendants(pred::Function, node::AnyTree, stop_traversal::Bool = false)::Vector{AnyTree} - out = [] + out = Vector{AnyTree}() if pred(node) push!(out, node) if stop_traversal From 1ba8b2eecbe18af740f5aa63b1bcc1d142c4c523 Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Mon, 26 Jan 2026 15:52:22 +0100 Subject: [PATCH 08/10] Solve more violations Also: - Removed AnalysisDemo.jl as it should not be part of our product --- checks/AvoidHardCodedNumbers.jl | 43 ++++++++++++------- checks/DoNotCommentOutCode.jl | 8 ++-- checks/DoNotNestMultilineComments.jl | 5 ++- checks/IndentationLevelsAreFourSpaces.jl | 5 +-- checks/IndentationOfModules.jl | 6 +-- checks/LeadingAndTrailingDigits.jl | 4 +- checks/LocationOfGlobalVariables.jl | 4 +- ...unctionsHaveATerminatingReturnStatement.jl | 4 +- checks/ModuleEndComment.jl | 4 +- checks/ModuleIncludeLocation.jl | 4 +- checks/ModuleNameCasing.jl | 4 +- checks/ModuleSingleImportLine.jl | 2 +- checks/OmitTrailingWhiteSpace.jl | 6 +-- checks/OneExpressionPerLine.jl | 4 +- checks/TooManyTypesInUnions.jl | 1 + checks/UseSpacesInsteadOfTabs.jl | 7 +-- src/Analysis.jl | 19 ++++---- src/AnalysisDemo.jl | 25 ----------- src/Properties.jl | 5 +-- src/SymbolTable.jl | 5 ++- src/SyntaxNodeHelpers.jl | 19 +++++--- test/TestConsistentLineEndings.jl | 3 +- 22 files changed, 98 insertions(+), 89 deletions(-) delete mode 100644 src/AnalysisDemo.jl diff --git a/checks/AvoidHardCodedNumbers.jl b/checks/AvoidHardCodedNumbers.jl index efdd837..8626f5e 100644 --- a/checks/AvoidHardCodedNumbers.jl +++ b/checks/AvoidHardCodedNumbers.jl @@ -4,8 +4,34 @@ using ...Properties: get_number, is_constant, is_global_decl, is_literal_number include("_common.jl") +""" Positive powers of 10 (up till 10^18) """ +const POWERS_OF_TEN_POSITIVE = Set{Int64}([10^i for i in 1:18]) + +""" Negative powers of 10 (up till -10^18) """ +const POWERS_OF_TEN_NEGATIVE = Set{Int64}([-i for i in POWERS_OF_TEN_POSITIVE]) + +""" Positive and negative powers of 10 """ +const POWERS_OF_TEN = POWERS_OF_TEN_POSITIVE ∪ POWERS_OF_TEN_NEGATIVE + +""" Positive powers of 2 (up till 2^20) """ +const POWERS_OF_TWO = Set{Int64}([2^i for i in 1:20]) + +""" Integers with special meaning, such as number of seconds, degrees, etc . """ +const SOME_SPECIAL_INTS = Set{Int64}([0, 1, + 60, # minutes, seconds + 90, 180, 270, 360 # degrees + ]) + +""" Integers that are not considered magical and may appear as constants. """ +const KNOWN_INTS = SOME_SPECIAL_INTS ∪ POWERS_OF_TEN ∪ POWERS_OF_TWO + +""" Floats that are not considered magical and may appear as constants. """ +const KNOWN_FLOATS = Set{Float64}([0.1, 0.01, 0.001, 0.0001, 0.5]) ∪ + Set{Float64}(convert.(Float64, POWERS_OF_TEN)) ∪ + Set{Float64}(convert.(Float64, SOME_SPECIAL_INTS)) + struct Check<:Analysis.Check - seen_before + seen_before::Set{Number} # FIXME Fine for integers but, for floats, we should # probably use a tolerance to compare them. @@ -56,21 +82,6 @@ end # TODO Add (unit?) tests -## Magic numbers -# Integers -const POWERS_OF_TEN_POSITIVE = Set{Int64}([10^i for i in 1:18]) -const POWERS_OF_TEN_NEGATIVE = Set{Int64}([-i for i in POWERS_OF_TEN_POSITIVE]) -const POWERS_OF_TEN = POWERS_OF_TEN_POSITIVE ∪ POWERS_OF_TEN_NEGATIVE -const POWERS_OF_TWO = Set{Int64}([2^i for i in 1:20]) -const SOME_SPECIAL_INTS = Set{Int64}([0, 1, - 60, # minutes, seconds - 90, 180, 270, 360 # degrees - ]) -const KNOWN_INTS = SOME_SPECIAL_INTS ∪ POWERS_OF_TEN ∪ POWERS_OF_TWO -# Floats -const KNOWN_FLOATS = Set{Float64}([0.1, 0.01, 0.001, 0.0001, 0.5]) ∪ - Set{Float64}(convert.(Float64, POWERS_OF_TEN)) ∪ - Set{Float64}(convert.(Float64, SOME_SPECIAL_INTS)) """ is_magic_number(node::SyntaxNode)::Bool diff --git a/checks/DoNotCommentOutCode.jl b/checks/DoNotCommentOutCode.jl index 8f6e120..28160c5 100644 --- a/checks/DoNotCommentOutCode.jl +++ b/checks/DoNotCommentOutCode.jl @@ -1,11 +1,13 @@ module DoNotCommentOutCode -using ...CommentHelpers: Comment, CommentBlock, get_comment_blocks, get_range, get_text, contains_comments +using ...CommentHelpers: Comment, CommentBlock, contains_comments, get_comment_blocks, get_range, get_text +using JuliaSyntax: kind, parseall, source_location using ...WhitespaceHelpers: combine_ranges -using JuliaSyntax: kind, @K_str, parseall, source_location include("_common.jl") +const CommentOrCommentBlock = Union{Comment, CommentBlock} + """ Some keywords and other signifiers that need to be in the string in order for it to be considered code @@ -60,7 +62,7 @@ function _contains_code(text::AbstractString)::Bool return true end -function _contains_code(comment::Union{Comment, CommentBlock})::Bool +function _contains_code(comment::CommentOrCommentBlock)::Bool return _contains_code(get_text(comment)) end diff --git a/checks/DoNotNestMultilineComments.jl b/checks/DoNotNestMultilineComments.jl index fb5a78b..0f9ae6a 100644 --- a/checks/DoNotNestMultilineComments.jl +++ b/checks/DoNotNestMultilineComments.jl @@ -5,13 +5,14 @@ using ...SyntaxNodeHelpers include("_common.jl") +""" Start of multiline comment in Julia """ +const ML_COMMENT = "#=" + struct Check<:Analysis.Check end Analysis.id(::Check) = "do-not-nest-multiline-comments" Analysis.severity(::Check) = 9 Analysis.synopsis(::Check) = "Don't nest multiline comments" -const ML_COMMENT = "#=" - function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_toplevel, node -> begin code = node.source.code diff --git a/checks/IndentationLevelsAreFourSpaces.jl b/checks/IndentationLevelsAreFourSpaces.jl index 6d17d40..6af17a1 100644 --- a/checks/IndentationLevelsAreFourSpaces.jl +++ b/checks/IndentationLevelsAreFourSpaces.jl @@ -1,9 +1,8 @@ module IndentationLevelsAreFourSpaces -include("_common.jl") - using ...Properties: is_toplevel -using ...SyntaxNodeHelpers + +include("_common.jl") struct Check<:Analysis.Check end Analysis.id(::Check) = "indentation-levels-are-four-spaces" diff --git a/checks/IndentationOfModules.jl b/checks/IndentationOfModules.jl index 042ceaf..1dba0bc3 100644 --- a/checks/IndentationOfModules.jl +++ b/checks/IndentationOfModules.jl @@ -1,12 +1,12 @@ module IndentationOfModules -include("_common.jl") - using JuliaSyntax: view using ...Properties: is_module, get_module_name using ...SyntaxNodeHelpers: ancestors using ...WhitespaceHelpers: normalized_green_child_range +include("_common.jl") + struct Check<:Analysis.Check end Analysis.id(::Check) = "indentation-of-modules" Analysis.severity(::Check) = 7 @@ -36,7 +36,7 @@ function _check(this::Check, ctxt::AnalysisContext, module_node::SyntaxNode)::No actual_indent = length(strip(view(module_node.source, ws_range), ['\r', '\n'])) # Only report on indent on current line (not newlines) if exp_indent != actual_indent - nudged_range = range(;stop=ws_range.stop, length=actual_indent) + nudged_range = range(; stop=ws_range.stop, length=actual_indent) report_violation(ctxt, this, nudged_range, "Contents of module '$module_name' should have an indentation of width $exp_indent, but found $actual_indent") end end diff --git a/checks/LeadingAndTrailingDigits.jl b/checks/LeadingAndTrailingDigits.jl index 9a281bb..285c286 100644 --- a/checks/LeadingAndTrailingDigits.jl +++ b/checks/LeadingAndTrailingDigits.jl @@ -7,7 +7,9 @@ using JuliaSyntax: sourcetext struct Check<:Analysis.Check end Analysis.id(::Check) = "leading-and-trailing-digits" Analysis.severity(::Check) = 3 -Analysis.synopsis(::Check) = "Floating-point numbers should always have one digit before the decimal point and at least one after" +function Analysis.synopsis(::Check) + return "Floating-point numbers should always have one digit before the decimal point and at least one after" +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"Float", n -> _check_float_node(this, ctxt, n)) diff --git a/checks/LocationOfGlobalVariables.jl b/checks/LocationOfGlobalVariables.jl index d9d4bfd..fa50049 100644 --- a/checks/LocationOfGlobalVariables.jl +++ b/checks/LocationOfGlobalVariables.jl @@ -7,7 +7,9 @@ using ...Properties: haschildren, is_export, is_global_decl, is_import, is_mod_t struct Check<:Analysis.Check end Analysis.id(::Check) = "location-of-global-variables" Analysis.severity(::Check) = 7 -Analysis.synopsis(::Check) = "Global variables should be placed at the top of a module or file" +function Analysis.synopsis(::Check) + return "Global variables should be placed at the top of a module or file" +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_global_decl, n -> _check(this, ctxt, n)) diff --git a/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl b/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl index 6c2adbd..8072ff3 100644 --- a/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl +++ b/checks/LongFormFunctionsHaveATerminatingReturnStatement.jl @@ -8,7 +8,9 @@ using ...WhitespaceHelpers: normalized_green_child_range struct Check<:Analysis.Check end Analysis.id(::Check) = "long-form-functions-have-a-terminating-return-statement" Analysis.severity(::Check) = 3 -Analysis.synopsis(::Check) = "Long form functions should end with an explicit return statement" +function Analysis.synopsis(::Check) + return "Long form functions should end with an explicit return statement" +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"function", node -> begin diff --git a/checks/ModuleEndComment.jl b/checks/ModuleEndComment.jl index 8305066..641dbb9 100644 --- a/checks/ModuleEndComment.jl +++ b/checks/ModuleEndComment.jl @@ -8,7 +8,9 @@ using ...Properties: is_module, is_toplevel, get_module_name struct Check<:Analysis.Check end Analysis.id(::Check) = "module-end-comment" Analysis.severity(::Check) = 9 -Analysis.synopsis(::Check) = "The end statement of a module should have a comment with the module name" +function Analysis.synopsis(::Check) + return "The end statement of a module should have a comment with the module name" +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) diff --git a/checks/ModuleIncludeLocation.jl b/checks/ModuleIncludeLocation.jl index a349800..fc082a7 100644 --- a/checks/ModuleIncludeLocation.jl +++ b/checks/ModuleIncludeLocation.jl @@ -7,7 +7,9 @@ include("_common.jl") struct Check<:Analysis.Check end Analysis.id(::Check) = "module-include-location" Analysis.severity(::Check) = 9 -Analysis.synopsis(::Check) = "The list of included files should be after the list of imported packages" +function Analysis.synopsis(::Check) + return "The list of included files should be after the list of imported packages" +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, is_module, n -> _check(this, ctxt, n)) diff --git a/checks/ModuleNameCasing.jl b/checks/ModuleNameCasing.jl index 80b0a7c..a514356 100644 --- a/checks/ModuleNameCasing.jl +++ b/checks/ModuleNameCasing.jl @@ -6,7 +6,9 @@ using ...Properties: get_module_name, is_upper_camel_case struct Check<:Analysis.Check end Analysis.id(::Check) = "module-name-casing" Analysis.severity(::Check) = 5 -Analysis.synopsis(::Check) = "Package names and module names should be written in UpperCamelCase" +function Analysis.synopsis(::Check) + return "Package names and module names should be written in UpperCamelCase" +end function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"module", node -> begin diff --git a/checks/ModuleSingleImportLine.jl b/checks/ModuleSingleImportLine.jl index f3dbb08..6df149c 100644 --- a/checks/ModuleSingleImportLine.jl +++ b/checks/ModuleSingleImportLine.jl @@ -15,7 +15,7 @@ end function _check(this::Check, ctxt::AnalysisContext, module_node::SyntaxNode)::Nothing @assert kind(module_node) == K"module" "Expected a [module] node, got [$(kind(module_node))]." - @assert numchildren(module_node) == 2 "This module has a weird shape: "* string(module_node) + @assert numchildren(module_node) == 2 "This module has a weird shape: " * string(module_node) @assert kind(children(module_node)[2]) == K"block" "The second child of a [module] node is not a [block]!" # Filters on using, import, include. diff --git a/checks/OmitTrailingWhiteSpace.jl b/checks/OmitTrailingWhiteSpace.jl index ea6766a..58e5f8d 100644 --- a/checks/OmitTrailingWhiteSpace.jl +++ b/checks/OmitTrailingWhiteSpace.jl @@ -1,9 +1,9 @@ module OmitTrailingWhiteSpace -include("_common.jl") - using ...Properties: is_toplevel +include("_common.jl") + struct Check<:Analysis.Check end Analysis.id(::Check) = "omit-trailing-white-space" Analysis.severity(::Check) = 7 @@ -20,7 +20,7 @@ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing line::Int = count("\n", code[1:m.offset]) + 1 col::Int = m.offset - something(findprev('\n', code, m.offset), 1) + 1 bufferrange = m.offset:m.offset + length(m.captures[1]) - report_violation(ctxt, this, (line,col), bufferrange, synopsis(this)) + report_violation(ctxt, this, (line, col), bufferrange, synopsis(this)) end return nothing end diff --git a/checks/OneExpressionPerLine.jl b/checks/OneExpressionPerLine.jl index 06dd157..99f7c01 100644 --- a/checks/OneExpressionPerLine.jl +++ b/checks/OneExpressionPerLine.jl @@ -31,7 +31,7 @@ Analyzing deeper within concatenated statements may lead to duplicate reporting or storing of global data (both of which is not wanted). """ function _check(this::Check, ctxt::AnalysisContext, node::SyntaxNode)::Nothing - lines_to_report = Set{Integer}() + lines_to_report = Set{Int}() nodes_to_check = _get_subnodes_to_check(node) for subnode in nodes_to_check lines_to_report = union!(lines_to_report, _find_semicolon_lines(subnode)) @@ -76,7 +76,7 @@ function _has_semicolon_without_newline(green_children, green_idx::Integer)::Boo end function _find_semicolon_lines(node::SyntaxNode)::Set{Integer} - lines_to_report = Set{Integer}() + lines_to_report = Set{Int}() offset = 0 green_children = children(node.raw) for green_idx in eachindex(green_children) diff --git a/checks/TooManyTypesInUnions.jl b/checks/TooManyTypesInUnions.jl index 76c94ed..fc3b531 100644 --- a/checks/TooManyTypesInUnions.jl +++ b/checks/TooManyTypesInUnions.jl @@ -8,6 +8,7 @@ Analysis.id(::Check) = "too-many-types-in-unions" Analysis.severity(::Check) = 6 Analysis.synopsis(::Check) = "Too many types in Unions" +""" Maximum number of generic arguments in a Union type. """ const MAX_UNION_TYPES = 4 function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing diff --git a/checks/UseSpacesInsteadOfTabs.jl b/checks/UseSpacesInsteadOfTabs.jl index cd5ae36..0d4b7c4 100644 --- a/checks/UseSpacesInsteadOfTabs.jl +++ b/checks/UseSpacesInsteadOfTabs.jl @@ -7,7 +7,8 @@ Analysis.id(::Check) = "use-spaces-instead-of-tabs" Analysis.severity(::Check) = 7 Analysis.synopsis(::Check) = "Use spaces instead of tabs for indentation" -const REGEX = r"(\s*)\t+.*" +""" Regular expression matching spaces at the beginning of the string. """ +const REGEX = r"^(\s*)\t+.*" function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing register_syntaxnode_action(ctxt, n -> kind(n) == K"toplevel", node -> begin @@ -15,12 +16,12 @@ function Analysis.init(this::Check, ctxt::AnalysisContext)::Nothing starts = node.source.line_starts successive_pairs = collect(zip(starts, Iterators.drop(starts, 1))) linenr::Int = 1 - for (start,stop) in successive_pairs + for (start, stop) in successive_pairs line::String = code[start:prevind(code, stop)] m = match(REGEX, line) if ! isnothing(m) offset::Int = length(m.captures[1]) - linepos = (linenr, offset+1) + linepos = (linenr, offset + 1) bufferrange = range(start + offset, length=1) report_violation(ctxt, this, linepos, bufferrange, synopsis(this)) end diff --git a/src/Analysis.jl b/src/Analysis.jl index 9b81a8b..2c5dee0 100644 --- a/src/Analysis.jl +++ b/src/Analysis.jl @@ -1,14 +1,13 @@ module Analysis +using JuliaSyntax: SyntaxNode, GreenNode, Kind, kind, sourcetext, source_location +import InteractiveUtils: subtypes + export AnalysisContext, Violation, run_analysis, register_syntaxnode_action, report_violation export Check, id, synopsis, severity, init export GreenLeaf, find_greenleaf, kind, sourcetext export dfs_traversal, find_syntaxnode_at_position, source_location -using JuliaSyntax - -import JuliaSyntax: SyntaxNode, GreenNode, Kind, kind, sourcetext, source_location -import InteractiveUtils: subtypes # Here to keep Properties importable as ..Properties by SymbolTable. # Mainly to ensure that it's imported in the same way by both @@ -16,6 +15,9 @@ import InteractiveUtils: subtypes using ..Properties using ..SymbolTable +const NullableGreenLeaf = Union{GreenLeaf, Nothing} +const NullableSyntaxNode = Union{SyntaxNode, Nothing} + "The abstract base type for all checks." abstract type Check end id(this::Check)::String = error("id() not implemented for this check") @@ -26,7 +28,7 @@ init(this::Check, ctxt) = error("init() not implemented for this check") struct Violation check::Check sourcefile::SourceFile - linepos::Tuple{Int,Int} # The line and column of the violation + linepos::Tuple{Int, Int} # The line and column of the violation bufferrange::UnitRange{Integer} # The character range in the source code msg::String end @@ -42,9 +44,6 @@ sourcetext(gl::GreenLeaf)::String = gl.sourcefile.code[gl.range] kind(gl::GreenLeaf) = kind(gl.node) source_location(gl::GreenLeaf) = source_location(gl.sourcefile, gl.range.start) -const NullableGreenLeaf = Union{GreenLeaf, Nothing} -const NullableSyntaxNode = Union{SyntaxNode, Nothing} - struct CheckRegistration predicate::Function # A predicate function that determines if the action applies to a SyntaxNode action::Function # The action to be performed on SyntaxNode when the predicate applies @@ -154,7 +153,7 @@ end Use `offsetspan` to specify the range of the violation relative to the node's position. """ function report_violation(ctxt::AnalysisContext, check::Check, node::SyntaxNode, msg::String; - offsetspan::Union{Nothing, Tuple{Int,Int}}=nothing + offsetspan::Union{Nothing, Tuple{Int, Int}}=nothing )::Nothing linepos = JuliaSyntax.source_location(node) bufferrange = JuliaSyntax.byte_range(node) @@ -171,7 +170,7 @@ end Reports a violation on location `linepos` and range `bufferrange` in the current context. """ function report_violation(ctxt::AnalysisContext, check::Check, - linepos::Tuple{Int,Int}, + linepos::Tuple{Int, Int}, bufferrange::UnitRange{Int}, msg::String )::Nothing diff --git a/src/AnalysisDemo.jl b/src/AnalysisDemo.jl deleted file mode 100644 index 79fdded..0000000 --- a/src/AnalysisDemo.jl +++ /dev/null @@ -1,25 +0,0 @@ -# This file can be used to invoke a single rule on a piece of code, useful during development -using InteractiveUtils - -using JuliaCheck - -using JuliaSyntax: SourceFile -using JuliaCheck.Analysis -using JuliaCheck.Analysis: Check, id -using JuliaCheck.Output: print_violations - -global checks = map(c -> c(), subtypes(Check)) -target_check = "type-names-upper-camel-case" -global checks1 = filter(c -> id(c) === target_check, checks) -@assert length(checks1) == 1 - -text = """ -struct transX end -struct TransX end -""" - -sourcefile = SourceFile(text, filename="dummy.jl") -printer = JuliaCheck.Output.select_violation_printer("highlighting") -violations = Analysis.run_analysis(sourcefile, checks1; print_ast=true, print_llt=true) -output_file_arg = "" -print_violations(printer, output_file_arg, violations) diff --git a/src/Properties.jl b/src/Properties.jl index 9418c91..7205e55 100644 --- a/src/Properties.jl +++ b/src/Properties.jl @@ -3,7 +3,7 @@ module Properties import JuliaSyntax: Kind, GreenNode, SyntaxNode, SourceFile, @K_str, @KSet_str, head, is_dotted, is_leaf, kind, numchildren, sourcetext, span, untokenize, JuliaSyntax as JS -export AnyTree, NullableNode, EOL, MAX_LINE_LENGTH, +export AnyTree, NullableNode, MAX_LINE_LENGTH, children, closes_module, closes_scope, expr_depth, expr_size, @@ -33,10 +33,9 @@ const NullableString = Union{String, Nothing} const NullableNode = Union{AnyTree, Nothing} const NodeAndString = Tuple{AnyTree, NullableString} - ## Global definitions +""" Maximum allowed length of a source line. """ const MAX_LINE_LENGTH = 92 -const EOL = (Sys.iswindows() ? "\n\r" : "\n") ## Functions diff --git a/src/SymbolTable.jl b/src/SymbolTable.jl index c4429c6..abcb576 100644 --- a/src/SymbolTable.jl +++ b/src/SymbolTable.jl @@ -32,9 +32,10 @@ When searching for a symbol, we scan the stack of scopes of the current module, top to bottom. Symbols from other modules have to be qualified, or entered into the current module's global scope with a `using` declaration. =# - const Scope = Dict{String, SymbolTableItem} + const NestedScopes = Stack{Scope} + """ A module containing an identifier and a stack of scopes. @@ -224,7 +225,7 @@ the abstract syntax tree. When a node is hit, this ensures that the syntax tree is updated as expected. The reason why this cannot easily be done as a part of other functionality -(for example, also making this use predicate behaviour like the rules do) +(for example, also making this use predicate behavior like the rules do) is that there is also a necessity to have this work on _exiting_ a node while preserving the state in between. diff --git a/src/SyntaxNodeHelpers.jl b/src/SyntaxNodeHelpers.jl index c32dda6..d859142 100644 --- a/src/SyntaxNodeHelpers.jl +++ b/src/SyntaxNodeHelpers.jl @@ -1,12 +1,12 @@ module SyntaxNodeHelpers -export ancestors, is_scope_construct, apply_to_operands, extract_special_value, find_node_at_position -export SpecialValue - using JuliaSyntax: SyntaxNode, GreenNode, kind, numchildren, children, source_location, is_operator, - is_infix_op_call, is_prefix_op_call, byte_range, is_leaf + is_infix_op_call, is_prefix_op_call, byte_range, is_leaf, NullableSyntaxNode import JuliaSyntax: @K_str, @KSet_str +export ancestors, is_scope_construct, apply_to_operands, extract_special_value, find_node_at_position +export SpecialValue + const AnyTree = Union{SyntaxNode, GreenNode} "Returns list of ancestors for given node, excluding self, ordered by increasing distance." @@ -38,10 +38,19 @@ function apply_to_operands(node::SyntaxNode, func::Function)::Nothing return nothing end +""" Identifiers representing Infinity. """ const INF_VALUES = Set(["Inf", "Inf16", "Inf32", "Inf64"]) + +""" Identifiers representing Not-a-Number floating point values. """ const NAN_VALUES = Set(["NaN", "NaN16", "NaN32", "NaN64"]) + +""" Identifiers representing Missing values. """ const MISSING_VALUES = Set(["missing", "Missing"]) + +""" Identifiers representing Nothing values. """ const NOTHING_VALUES = Set(["nothing", "Nothing"]) + +""" Set of all special values. """ const SPECIAL_VALUES = union(INF_VALUES, NAN_VALUES, MISSING_VALUES, NOTHING_VALUES) """ @@ -104,7 +113,7 @@ end Finds deepest node containing the given `pos`. If there is no `SyntaxNode` that contains the position, the `toplevel` node is returned. """ -function find_node_at_position(node::SyntaxNode, pos::Integer)::Union{SyntaxNode,Nothing} +function find_node_at_position(node::SyntaxNode, pos::Integer)::NullableSyntaxNode # Check if the current node contains the position if ! (pos in byte_range(node)) return nothing diff --git a/test/TestConsistentLineEndings.jl b/test/TestConsistentLineEndings.jl index 008a0b8..0b33dd8 100644 --- a/test/TestConsistentLineEndings.jl +++ b/test/TestConsistentLineEndings.jl @@ -6,7 +6,6 @@ prevent these from being auto-converted out by e.g. Git or VSCode. The line endings of this file itself should always be LF, see .gitattributes =# @testitem "ConsistentLineEndings.jl" begin - include("../src/JuliaCheck.jl") using .JuliaCheck.Analysis using .JuliaCheck.Output using JuliaSyntax: SourceFile @@ -80,7 +79,7 @@ The line endings of this file itself should always be LF, see .gitattributes printer = JuliaCheck.Output.select_violation_printer("highlighting") output_file_arg = "" result = IOCapture.capture() do - violations = Analysis.run_analysis(source, checks) + violations = Analysis.run_analysis(source, checks) print_violations(printer, output_file_arg, violations) end @test replace(result.output, r"\r\n?" => "\n") == exp # In the output, we do not need to compare line endings From 22dc22b2c7134751f666720acbeb0841c5a6e173 Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Mon, 26 Jan 2026 16:12:25 +0100 Subject: [PATCH 09/10] Fix failing test --- src/Analysis.jl | 10 ++++++---- src/SyntaxNodeHelpers.jl | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Analysis.jl b/src/Analysis.jl index 2c5dee0..7e668f0 100644 --- a/src/Analysis.jl +++ b/src/Analysis.jl @@ -1,6 +1,8 @@ module Analysis -using JuliaSyntax: SyntaxNode, GreenNode, Kind, kind, sourcetext, source_location +using JuliaSyntax + +import JuliaSyntax: SyntaxNode, GreenNode, kind, sourcetext, source_location import InteractiveUtils: subtypes export AnalysisContext, Violation, run_analysis, register_syntaxnode_action, report_violation @@ -15,9 +17,6 @@ export dfs_traversal, find_syntaxnode_at_position, source_location using ..Properties using ..SymbolTable -const NullableGreenLeaf = Union{GreenLeaf, Nothing} -const NullableSyntaxNode = Union{SyntaxNode, Nothing} - "The abstract base type for all checks." abstract type Check end id(this::Check)::String = error("id() not implemented for this check") @@ -44,6 +43,9 @@ sourcetext(gl::GreenLeaf)::String = gl.sourcefile.code[gl.range] kind(gl::GreenLeaf) = kind(gl.node) source_location(gl::GreenLeaf) = source_location(gl.sourcefile, gl.range.start) +const NullableGreenLeaf = Union{GreenLeaf, Nothing} +const NullableSyntaxNode = Union{SyntaxNode, Nothing} + struct CheckRegistration predicate::Function # A predicate function that determines if the action applies to a SyntaxNode action::Function # The action to be performed on SyntaxNode when the predicate applies diff --git a/src/SyntaxNodeHelpers.jl b/src/SyntaxNodeHelpers.jl index d859142..a6c77f8 100644 --- a/src/SyntaxNodeHelpers.jl +++ b/src/SyntaxNodeHelpers.jl @@ -1,7 +1,7 @@ module SyntaxNodeHelpers using JuliaSyntax: SyntaxNode, GreenNode, kind, numchildren, children, source_location, is_operator, - is_infix_op_call, is_prefix_op_call, byte_range, is_leaf, NullableSyntaxNode + is_infix_op_call, is_prefix_op_call, byte_range, is_leaf import JuliaSyntax: @K_str, @KSet_str export ancestors, is_scope_construct, apply_to_operands, extract_special_value, find_node_at_position From f742e8d34f8e949b5eeb7578c0efa44628133fb3 Mon Sep 17 00:00:00 2001 From: reniers-tiobe Date: Wed, 28 Jan 2026 12:24:28 +0100 Subject: [PATCH 10/10] Add "src/printers" to JuliaCheck self and fix most violations --- src/printers/HighlightingViolationPrinter.jl | 21 ++++++++++---------- src/printers/JSONViolationPrinter.jl | 16 +++++++-------- src/printers/SimpleViolationPrinter.jl | 11 +++++----- test/runtests.jl | 11 ++++++---- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/printers/HighlightingViolationPrinter.jl b/src/printers/HighlightingViolationPrinter.jl index 7b1c146..e8ad7cb 100644 --- a/src/printers/HighlightingViolationPrinter.jl +++ b/src/printers/HighlightingViolationPrinter.jl @@ -1,14 +1,14 @@ module HighlightingViolationPrinter -import ...Analysis: Violation, severity, id, synopsis -import ..Output: shorthand, requiresfile, print_violations; using ..Output -import JuliaSyntax: SourceFile, JuliaSyntax as JS +using ...Analysis: Violation, severity, id, synopsis +using JuliaSyntax: filename, highlight, SourceFile +using ..Output struct ViolationPrinter<:Output.ViolationPrinter end -shorthand(::ViolationPrinter) = "highlighting" -requiresfile(::ViolationPrinter) = false +Output.shorthand(::ViolationPrinter) = "highlighting" +Output.requiresfile(::ViolationPrinter) = false -function print_violations(this::ViolationPrinter, outputfile::String, violations::Vector{Violation})::Nothing +function Output.print_violations(this::ViolationPrinter, outputfile::String, violations::Vector{Violation})::Nothing append_period(s::String) = endswith(s, ".") ? s : s * "." for v in violations _report_violation( @@ -29,16 +29,17 @@ end function _report_violation(sourcefile::SourceFile; index::Int, len::Int, line::Int, col::Int, severity::Int, user_msg::String, summary::String, rule_id::String)::Nothing - printstyled("\n$(JS.filename(sourcefile))($line, $col):\n"; + printstyled("\n$(filename(sourcefile))($line, $col):\n"; underline=true) - JS.highlight(stdout, sourcefile, index:index+len-1; - note=user_msg, notecolor=:yellow, - context_lines_after=0, context_lines_before=0) + highlight(stdout, sourcefile, range(index; length=len); + note=user_msg, notecolor=:yellow, + context_lines_after=0, context_lines_before=0) printstyled("\n$summary"; color=:cyan) printstyled("\nRule:"; underline=true) printstyled(" $rule_id. ") printstyled("Severity:"; underline=true) printstyled(" $severity\n") + return nothing end end # module HighlightingViolationPrinter diff --git a/src/printers/JSONViolationPrinter.jl b/src/printers/JSONViolationPrinter.jl index 743df62..b935b62 100644 --- a/src/printers/JSONViolationPrinter.jl +++ b/src/printers/JSONViolationPrinter.jl @@ -1,13 +1,13 @@ module JSONViolationPrinter -import ...Analysis: Violation, severity, id, synopsis -import ..Output: shorthand, requiresfile, print_violations; using ..Output -import JuliaSyntax: filename, source_location, SourceFile -import JSON3 +using ...Analysis: Violation, severity, id, synopsis +using JSON3 +using JuliaSyntax: filename, source_location +using ..Output struct ViolationPrinter<:Output.ViolationPrinter end -shorthand(::ViolationPrinter) = "json" -requiresfile(::ViolationPrinter) = true +Output.shorthand(::ViolationPrinter) = "json" +Output.requiresfile(::ViolationPrinter) = true Base.@kwdef struct ViolationOutput line_start::Int64 @@ -22,9 +22,9 @@ Base.@kwdef struct ViolationOutput url::String end -function print_violations(this::ViolationPrinter, outputfile::String, violations::Vector{Violation})::Nothing +function Output.print_violations(this::ViolationPrinter, outputfile::String, violations::Vector{Violation})::Nothing append_period(s::String) = endswith(s, ".") ? s : s * "." - output_violations = [] + output_violations = Vector{ViolationOutput}() for v in violations l_end, c_end = source_location(v.sourcefile, v.bufferrange.stop) push!(output_violations, ViolationOutput( diff --git a/src/printers/SimpleViolationPrinter.jl b/src/printers/SimpleViolationPrinter.jl index 1540c35..4a72599 100644 --- a/src/printers/SimpleViolationPrinter.jl +++ b/src/printers/SimpleViolationPrinter.jl @@ -1,14 +1,13 @@ module SimpleViolationPrinter -import ...Analysis: Violation, severity, id -import ..Output: shorthand, requiresfile, print_violations; using ..Output -import JuliaSyntax: SourceFile +using ...Analysis: Violation, severity, id +using ..Output struct ViolationPrinter<:Output.ViolationPrinter end -shorthand(::ViolationPrinter) = "simple" -requiresfile(::ViolationPrinter) = false +Output.shorthand(::ViolationPrinter) = "simple" +Output.requiresfile(::ViolationPrinter) = false -function print_violations(this::ViolationPrinter, outputfile::String, violations::Vector{Violation})::Nothing +function Output.print_violations(this::ViolationPrinter, outputfile::String, violations::Vector{Violation})::Nothing if length(violations) == 0 println("No violations found.") else diff --git a/test/runtests.jl b/test/runtests.jl index cddb187..e561a67 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -127,7 +127,9 @@ end normalize(text) = strip(replace(text, "\r\n" => "\n", "\\" => "/")) * "\n" cd("res") do - @testset for printer in JuliaCheck.Output.get_available_printers() + printers = JuliaCheck.Output.get_available_printers() + @test length(printers) >= 3 + @testset for printer in printers printer_cmd = JuliaCheck.Output.shorthand(printer) printer_file = "ViolationPrinter-$(printer_cmd).out" val_file = "ViolationPrinter-$(printer_cmd).val" @@ -221,9 +223,10 @@ end @testitem "JuliaCheck self" begin import IOCapture isjuliafile = f -> endswith(f, ".jl") - checkfiles = filter(isjuliafile, readdir(joinpath(dirname(@__DIR__), "checks"), join=true)) - srcfiles = filter(isjuliafile, readdir(joinpath(dirname(@__DIR__), "src"), join=true)) - files = union(checkfiles, srcfiles) + files = collect(Iterators.flatten(map( + dir -> filter(isjuliafile, readdir(joinpath(dirname(@__DIR__), dir), join=true)), + ["checks", "src", joinpath("src", "printers")] + ))) args = ["--"] for file in files