RULEAPI-788 Validate use of diff-id and diff-type (#2790)
The validation is not yet enabled on CI checks because there exist too many errors in existing rule descriptions at the moment.
This commit is contained in:
parent
9326a648b6
commit
f57a9805b5
@ -87,7 +87,7 @@ validate_metadata_task:
|
|||||||
- ./ci/validate_metadata.sh
|
- ./ci/validate_metadata.sh
|
||||||
|
|
||||||
validate_ci_tests_task:
|
validate_ci_tests_task:
|
||||||
skip: "!changesInclude('ci_tests/*', 'ci/*')"
|
skip: "!changesInclude('ci_tests/**', 'ci/**')"
|
||||||
eks_container:
|
eks_container:
|
||||||
<<: *CONTAINER_DEFINITION
|
<<: *CONTAINER_DEFINITION
|
||||||
dockerfile: ci/Dockerfile
|
dockerfile: ci/Dockerfile
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
#!/usr/bin/ruby
|
#!/usr/bin/env ruby
|
||||||
# Based on asciidoctor main ruby script.
|
# Based on asciidoctor main ruby script.
|
||||||
# This is only meant to introspect and log which asciidoc files were used.
|
# This is only meant to introspect and log which asciidoc files were used.
|
||||||
|
|
||||||
require 'asciidoctor'
|
require 'asciidoctor'
|
||||||
require 'asciidoctor/cli'
|
require 'asciidoctor/cli'
|
||||||
|
|
||||||
|
MAIN_FILE_REGEX = /^.*\/rules\/(?<id>S\d+)\/(?:(?<lang>\w+)\/)?rule.adoc$/
|
||||||
|
|
||||||
class MainFileLogger < Asciidoctor::Extensions::Preprocessor
|
class MainFileLogger < Asciidoctor::Extensions::Preprocessor
|
||||||
include Asciidoctor::Logging
|
include Asciidoctor::Logging
|
||||||
|
|
||||||
def process document, reader
|
def process document, reader
|
||||||
|
# Enable sourcemap to track source location.
|
||||||
|
# This is useful to report more accurate errors in other loggers.
|
||||||
|
document.sourcemap = true
|
||||||
|
|
||||||
main_file = document.normalize_system_path(reader.file, document.reader.dir)
|
main_file = document.normalize_system_path(reader.file, document.reader.dir)
|
||||||
|
|
||||||
# This assumes unix-style path separator.
|
# This assumes unix-style path separator.
|
||||||
if nil == main_file.match(/^.*\/rules\/S\d+\/(\w+\/)?rule.adoc$/)
|
if nil == main_file.match(MAIN_FILE_REGEX)
|
||||||
abort("Main file does not follow expected pattern: #{main_file}")
|
abort("Main file does not follow expected pattern: #{main_file}")
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -73,9 +79,87 @@ class IncludeLogger < Asciidoctor::Extensions::IncludeProcessor
|
|||||||
# Intentionnaly no process function here.
|
# Intentionnaly no process function here.
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class SourceLogger < Asciidoctor::Extensions::TreeProcessor
|
||||||
|
include Asciidoctor::Logging
|
||||||
|
|
||||||
|
def get_source_location block
|
||||||
|
loc = block.source_location # Asciidoctor::Reader::Cursor.
|
||||||
|
return "#{loc.file}:#{loc.lineno}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_rule document
|
||||||
|
main_file = document.normalize_system_path(document.reader.file, document.reader.dir)
|
||||||
|
res = main_file.match(MAIN_FILE_REGEX)
|
||||||
|
lang = res[:lang] || 'default'
|
||||||
|
"#{res[:id]}/#{lang}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def process document
|
||||||
|
rule = get_rule(document)
|
||||||
|
source_blocks = document.find_by(context: :listing, style: 'source')
|
||||||
|
|
||||||
|
# Collect individually valid blocks per diff-id.
|
||||||
|
blocks_per_id = Hash.new { |hash, key| hash[key] = Array.new }
|
||||||
|
|
||||||
|
# Find blocks with only diff-id but no diff-type, or vice-versa, or invalid diff-type.
|
||||||
|
source_blocks.each do |block|
|
||||||
|
id = block.attr('diff-id', nil)
|
||||||
|
type = block.attr('diff-type', nil)
|
||||||
|
|
||||||
|
if !id and !type
|
||||||
|
next # Nothing to validate
|
||||||
|
end
|
||||||
|
|
||||||
|
loc = get_source_location(block)
|
||||||
|
|
||||||
|
if !id
|
||||||
|
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] diff-id is missing in #{loc}")
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
if !type
|
||||||
|
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] diff-type is missing in #{loc}")
|
||||||
|
next
|
||||||
|
elsif !['compliant', 'noncompliant'].include?(type)
|
||||||
|
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] diff-type '#{type}' is not valid in #{loc}")
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# The block is valid on its own.
|
||||||
|
blocks_per_id[id].push(block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Each diff-id should have exactly 1 compliant and 1 noncompliant block.
|
||||||
|
# Find blocks that break this rule.
|
||||||
|
blocks_per_id.each do |id, blocks|
|
||||||
|
# Sort to ensure deterministic output.
|
||||||
|
blocks.sort_by! { |block| get_source_location(block) }
|
||||||
|
|
||||||
|
case blocks.length
|
||||||
|
when 1
|
||||||
|
loc = get_source_location(blocks[0])
|
||||||
|
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] Incomplete example for diff-id=#{id}, missing counterpart for #{loc}")
|
||||||
|
when 2
|
||||||
|
type1 = blocks[0].attr('diff-type')
|
||||||
|
type2 = blocks[1].attr('diff-type')
|
||||||
|
if type1 == type2
|
||||||
|
loc1 = get_source_location(blocks[0])
|
||||||
|
loc2 = get_source_location(blocks[1])
|
||||||
|
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] Two #{type1} examples for diff-id=#{id}: #{loc1} and #{loc2}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
locs = blocks.map { |block| get_source_location(block) }
|
||||||
|
locs = locs.join(', ')
|
||||||
|
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] #{blocks.length} examples for diff-id=#{id}: #{locs}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Asciidoctor::Extensions.register do
|
Asciidoctor::Extensions.register do
|
||||||
preprocessor MainFileLogger
|
preprocessor MainFileLogger
|
||||||
include_processor IncludeLogger.new @document
|
include_processor IncludeLogger.new @document
|
||||||
|
treeprocessor SourceLogger
|
||||||
end
|
end
|
||||||
|
|
||||||
invoker = Asciidoctor::Cli::Invoker.new ARGV
|
invoker = Asciidoctor::Cli::Invoker.new ARGV
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
# asciidoctor: INFO: ASCIIDOC LOGGER MAIN FILE: $PATH
|
# asciidoctor: INFO: ASCIIDOC LOGGER MAIN FILE: $PATH
|
||||||
# asciidoctor: INFO: ASCIIDOC LOGGER INCLUDED: $PATH
|
# asciidoctor: INFO: ASCIIDOC LOGGER INCLUDED: $PATH
|
||||||
# asciidoctor: INFO: ASCIIDOC LOGGER CROSSREFERENCE: $RULEID cross-references $PATH
|
# asciidoctor: INFO: ASCIIDOC LOGGER CROSSREFERENCE: $RULEID cross-references $PATH
|
||||||
|
# asciidoctor: INFO: ASCIIDOC LOGGER DIFF: $VALIDATION_FAILURE_MESSAGE
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@ -39,21 +40,19 @@ grep_nofail() {
|
|||||||
grep "$@" || [ "$?" == "1" ]
|
grep "$@" || [ "$?" == "1" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extract_messages_from_log() {
|
||||||
|
# The first 3 columns of the log are not relevant.
|
||||||
|
# The 4th (and any that follows if ':' is used in the message)
|
||||||
|
# provides the relevant validation error message.
|
||||||
|
cut -d ':' -f 4- | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
find "${RULES_DIR}" -name 'rule.adoc' \
|
find "${RULES_DIR}" -name 'rule.adoc' \
|
||||||
| xargs "${SCRIPT_DIR}/custom-asciidoctor" -a rspecator-view --verbose -R "${RULES_DIR}" -D "${TMPOUT_DIR}" 2>&1 \
|
| xargs "${SCRIPT_DIR}/custom-asciidoctor" -a rspecator-view --verbose -R "${RULES_DIR}" -D "${TMPOUT_DIR}" 2>&1 \
|
||||||
| grep_nofail -e 'ASCIIDOC LOGGER' \
|
| grep_nofail -e 'ASCIIDOC LOGGER' \
|
||||||
> "${TMPOUT_DIR}/asciidoc_introspection"
|
> "${TMPOUT_DIR}/asciidoc_introspection"
|
||||||
|
|
||||||
grep -ve 'CROSSREFERENCE' "${TMPOUT_DIR}/asciidoc_introspection" \
|
cross_references=$(grep_nofail -e 'CROSSREFERENCE' "${TMPOUT_DIR}/asciidoc_introspection" | extract_messages_from_log)
|
||||||
| cut -d ':' -f 4 \
|
|
||||||
| sort -u \
|
|
||||||
> "${TMPOUT_DIR}/used_asciidoc_files"
|
|
||||||
|
|
||||||
git ls-files --cached -- "${RULES_DIR}/**.adoc" "${SHARED_CONTENT_DIR}/**.adoc" \
|
|
||||||
| xargs realpath \
|
|
||||||
> "${TMPOUT_DIR}/all_asciidoc_files"
|
|
||||||
|
|
||||||
cross_references=$(grep_nofail -e 'CROSSREFERENCE' "${TMPOUT_DIR}/asciidoc_introspection" | cut -d ':' -f 4 | sort -u)
|
|
||||||
if [[ -n "$cross_references" ]]; then
|
if [[ -n "$cross_references" ]]; then
|
||||||
echo >&2 'ERROR: Some rules try to include content from unallowed directories.'
|
echo >&2 'ERROR: Some rules try to include content from unallowed directories.'
|
||||||
echo >&2 'To share content between rules, you should use the "shared_content" folder at the root of the repository.'
|
echo >&2 'To share content between rules, you should use the "shared_content" folder at the root of the repository.'
|
||||||
@ -62,10 +61,29 @@ if [[ -n "$cross_references" ]]; then
|
|||||||
exit_code=1
|
exit_code=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
grep_nofail -Pe '(INCLUDE|MAIN FILE)' "${TMPOUT_DIR}/asciidoc_introspection" \
|
||||||
|
| extract_messages_from_log \
|
||||||
|
> "${TMPOUT_DIR}/used_asciidoc_files"
|
||||||
|
|
||||||
|
git ls-files --cached -- "${RULES_DIR}/**.adoc" "${SHARED_CONTENT_DIR}/**.adoc" \
|
||||||
|
| xargs realpath \
|
||||||
|
> "${TMPOUT_DIR}/all_asciidoc_files"
|
||||||
|
|
||||||
orphans=$(comm -1 -3 <(sort -u "${TMPOUT_DIR}/used_asciidoc_files") <(sort -u "${TMPOUT_DIR}/all_asciidoc_files"))
|
orphans=$(comm -1 -3 <(sort -u "${TMPOUT_DIR}/used_asciidoc_files") <(sort -u "${TMPOUT_DIR}/all_asciidoc_files"))
|
||||||
if [[ -n "$orphans" ]]; then
|
if [[ -n "$orphans" ]]
|
||||||
|
then
|
||||||
printf >&2 'ERROR: These adoc files are not included anywhere:\n-----\n%s\n-----\n' "$orphans"
|
printf >&2 'ERROR: These adoc files are not included anywhere:\n-----\n%s\n-----\n' "$orphans"
|
||||||
exit_code=1
|
exit_code=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
bad_diffs=$(grep_nofail -e 'DIFF' "${TMPOUT_DIR}/asciidoc_introspection" | extract_messages_from_log)
|
||||||
|
if [[ -n "$bad_diffs" ]]
|
||||||
|
then
|
||||||
|
printf >&2 'ERROR: Diff highlighting is used incorrectly:\n-----\n%s\n-----\n' "$bad_diffs"
|
||||||
|
if [[ -n "${RUNNING_CI_INTEGRATION_TEST:-}" ]]
|
||||||
|
then
|
||||||
|
exit_code=1 # FIXME: there are currently validation errors in the repo. Unconditionally enable this line when they are all fixed.
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
exit $exit_code
|
exit $exit_code
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
|
export RUNNING_CI_INTEGRATION_TEST=1 # FIXME remove this once it's no longer needed in validate.sh
|
||||||
|
|
||||||
# We could write complex checks to ensure only specific commands fail and emit
|
# We could write complex checks to ensure only specific commands fail and emit
|
||||||
# a specific error message. Instead, we rely on `set -xe` to consistently and
|
# a specific error message. Instead, we rely on `set -xe` to consistently and
|
||||||
# reliably exit with non-zero if any command fails and pinpoint which command
|
# reliably exit with non-zero if any command fails and pinpoint which command
|
||||||
@ -77,4 +79,22 @@ run_test "test_bad_cross_ref" \
|
|||||||
"S100 cross-references .*rules/S1000/bad.adoc" \
|
"S100 cross-references .*rules/S1000/bad.adoc" \
|
||||||
"S1000 cross-references .*rules/S100/java/bad.adoc"
|
"S1000 cross-references .*rules/S100/java/bad.adoc"
|
||||||
|
|
||||||
|
run_test "test_diff_source" \
|
||||||
|
"ERROR: Diff highlighting is used incorrectly:" \
|
||||||
|
"\[S100/cfamily] diff-type is missing in .*/rules/S100/cfamily/rule.adoc:3" \
|
||||||
|
"\[S100/cfamily] diff-id is missing in .*/rules/S100/cfamily/rule.adoc:8" \
|
||||||
|
"\[S100/cfamily] diff-type 'bad' is not valid in .*/rules/S100/cfamily/rule.adoc:13" \
|
||||||
|
"\[S100/cfamily] diff-type is missing in .*/rules/S100/cfamily/local.adoc:3" \
|
||||||
|
"\[S100/cfamily] diff-id is missing in .*/rules/S100/cfamily/local.adoc:8" \
|
||||||
|
"\[S100/cfamily] diff-type 'local' is not valid in .*/rules/S100/cfamily/local.adoc:13" \
|
||||||
|
"\[S100/cfamily] diff-type is missing in .*/shared_content/cfamily/shared.adoc:3" \
|
||||||
|
"\[S100/cfamily] diff-id is missing in .*/shared_content/cfamily/shared.adoc:8" \
|
||||||
|
"\[S100/cfamily] diff-type 'shared' is not valid in .*/shared_content/cfamily/shared.adoc:13" \
|
||||||
|
"\[S100/java] Incomplete example for diff-id=1, missing counterpart for .*/rules/S100/java/rule.adoc:3" \
|
||||||
|
"\[S100/java] Incomplete example for diff-id=2, missing counterpart for .*/rules/S100/java/rule.adoc:8" \
|
||||||
|
"\[S100/java] Incomplete example for diff-id=3, missing counterpart for .*/shared_content/java/example.adoc:3" \
|
||||||
|
"\[S100/java] 3 examples for diff-id=4: .*/rules/S100/java/rule.adoc:15, .*/shared_content/java/example.adoc:13, .*/shared_content/java/example.adoc:8" \
|
||||||
|
"\[S200/default] 3 examples for diff-id=1: .*/rules/S200/rule.adoc:12, .*/rules/S200/rule.adoc:2, .*/rules/S200/rule.adoc:7" \
|
||||||
|
"\[S200/default] Two noncompliant examples for diff-id=2: .*/rules/S200/rule.adoc:17 and .*/rules/S200/rule.adoc:22"
|
||||||
|
|
||||||
echo "All tests passed"
|
echo "All tests passed"
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
[source,cpp,diff-id=1]
|
||||||
|
----
|
||||||
|
Missing diff-type
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,cpp,diff-type=compliant]
|
||||||
|
----
|
||||||
|
Missing diff-id
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,c,diff-id=1,diff-type=local]
|
||||||
|
----
|
||||||
|
Bad diff-type
|
||||||
|
----
|
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
[source,cpp,diff-id=1]
|
||||||
|
----
|
||||||
|
Missing diff-type
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,cpp,diff-type=compliant]
|
||||||
|
----
|
||||||
|
Missing diff-id
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,c,diff-id=1,diff-type=bad]
|
||||||
|
----
|
||||||
|
Bad diff-type
|
||||||
|
----
|
||||||
|
|
||||||
|
include::./local.adoc[]
|
||||||
|
|
||||||
|
include::../../../shared_content/cfamily/shared.adoc[]
|
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
[source,java,diff-id=1,diff-type=compliant]
|
||||||
|
----
|
||||||
|
1. compliant but missing noncompliant
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,java,diff-id=2,diff-type=noncompliant]
|
||||||
|
----
|
||||||
|
2. noncompliant but missing compliant
|
||||||
|
----
|
||||||
|
|
||||||
|
include::../../../shared_content/java/example.adoc[]
|
||||||
|
|
||||||
|
[source,java,diff-id=4,diff-type=noncompliant]
|
||||||
|
----
|
||||||
|
4. noncompliant B
|
||||||
|
----
|
@ -0,0 +1,24 @@
|
|||||||
|
[source,diff-id=1,diff-type=compliant]
|
||||||
|
----
|
||||||
|
1. compliant
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,diff-id=1,diff-type=noncompliant]
|
||||||
|
----
|
||||||
|
1. noncompliant A
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,diff-id=1,diff-type=noncompliant]
|
||||||
|
----
|
||||||
|
1. noncompliant B
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,diff-id=2,diff-type=noncompliant]
|
||||||
|
----
|
||||||
|
2. noncompliant A
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,diff-id=2,diff-type=noncompliant]
|
||||||
|
----
|
||||||
|
2. noncompliant B
|
||||||
|
----
|
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
[source,cpp,diff-id=1]
|
||||||
|
----
|
||||||
|
Missing diff-type
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,cpp,diff-type=compliant]
|
||||||
|
----
|
||||||
|
Missing diff-id
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,c,diff-id=1,diff-type=shared]
|
||||||
|
----
|
||||||
|
Bad diff-type
|
||||||
|
----
|
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
[source,java,diff-id=3,diff-type=compliant]
|
||||||
|
----
|
||||||
|
3. compliant but missing noncompliant
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,java,diff-id=4,diff-type=compliant]
|
||||||
|
----
|
||||||
|
4. compliant but too many noncompliant
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,java,diff-id=4,diff-type=noncompliant]
|
||||||
|
----
|
||||||
|
4. noncompliant A
|
||||||
|
----
|
@ -3,3 +3,13 @@ S100 cfamily
|
|||||||
include::./another.adoc[]
|
include::./another.adoc[]
|
||||||
|
|
||||||
include::../../../shared_content/anything.adoc[]
|
include::../../../shared_content/anything.adoc[]
|
||||||
|
|
||||||
|
[source,cpp,diff-id=1,diff-type=noncompliant]
|
||||||
|
----
|
||||||
|
noncompliant
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,cpp,diff-id=1,diff-type=compliant]
|
||||||
|
----
|
||||||
|
compliant
|
||||||
|
----
|
||||||
|
Loading…
x
Reference in New Issue
Block a user