From b9743c8e5b8f53f4f7318fe9ea47b241cb75336d Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:15:21 +0200 Subject: [PATCH 01/16] Auto detect neo4j directory to start/stop a not yet updated version --- scripts/startNeo4j.sh | 27 +++++++++++++++++++-------- scripts/stopNeo4j.sh | 28 ++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/scripts/startNeo4j.sh b/scripts/startNeo4j.sh index 17be4be1c..890ac5d70 100755 --- a/scripts/startNeo4j.sh +++ b/scripts/startNeo4j.sh @@ -10,17 +10,12 @@ # Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands) set -o errexit -o pipefail +# Overrideable Defaults NEO4J_EDITION=${NEO4J_EDITION:-"community"} # Choose "community" or "enterprise" NEO4J_VERSION=${NEO4J_VERSION:-"5.21.2"} TOOLS_DIRECTORY=${TOOLS_DIRECTORY:-"tools"} # Get the tools directory (defaults to "tools") NEO4J_HTTP_PORT=${NEO4J_HTTP_PORT:-"7474"} # Neo4j's own "Bolt Protocol" port -# Internal Constants -NEO4J_DIR="${TOOLS_DIRECTORY}/neo4j-${NEO4J_EDITION}-${NEO4J_VERSION}" -NEO4J_DIR_WINDOWS="${TOOLS_DIRECTORY}\neo4j-${NEO4J_EDITION}-${NEO4J_VERSION}" -NEO4J_BIN="${NEO4J_DIR}/bin" -NEO4J_BIN_WINDOWS="${NEO4J_DIR_WINDOWS}\bin" - ## Get this "scripts" directory if not already set # Even if $BASH_SOURCE is made for Bourne-like shells it is also supported by others and therefore here the preferred solution. # CDPATH reduces the scope of the cd command to potentially prevent unintended directory changes. @@ -34,6 +29,22 @@ if [ -z "${TOOLS_DIRECTORY}" ]; then exit 1 fi +# Detect Neo4j directory within the "tools" directory +neo4j_directory="neo4j-${NEO4J_EDITION}-${NEO4J_VERSION}" # Default directory using environment variables +if [ ! -d "${TOOLS_DIRECTORY}/${neo4j_directory}" ] ; then + # Default directory doesn't exist. Try to find one with another version. + # This usually happens when Neo4j had been updated while a local project is still using an older version. + neo4j_directories=$(find "./${TOOLS_DIRECTORY}" -type d -name "neo4j-${NEO4J_EDITION}-*" | sort -r | head -n 1) + neo4j_directory=$(basename -- "$neo4j_directories") + echo "startNeo4j: Auto detected Neo4j directory within the tools directory: ${neo4j_directory}" +fi + +# Internal Constants +NEO4J_DIR="${TOOLS_DIRECTORY}/${neo4j_directory}" +NEO4J_DIR_WINDOWS="${TOOLS_DIRECTORY}\\${neo4j_directory}" +NEO4J_BIN="${NEO4J_DIR}/bin" +NEO4J_BIN_WINDOWS="${NEO4J_DIR_WINDOWS}\bin" + # Check if Neo4j is installed if [ -d "${NEO4J_BIN}" ] ; then echo "startNeo4j: Using Neo4j binary directory ${NEO4J_BIN}" @@ -51,7 +62,7 @@ source "${SCRIPTS_DIR}/waitForNeo4jHttpFunctions.sh" # Check if Neo4j is stopped (not running) using a temporary NEO4J_HOME environment variable that points to the current installation isDatabaseReady=$(isDatabaseQueryable) if [[ ${isDatabaseReady} == "false" ]]; then - echo "startNeo4j: Starting neo4j-${NEO4J_EDITION}-${NEO4J_VERSION} in ${NEO4J_DIR}" + echo "startNeo4j: Starting ${neo4j_directory} in ${NEO4J_DIR}" # Check if there is already a process that listens to the Neo4j HTTP port if isWindows; then @@ -88,5 +99,5 @@ if [[ ${isDatabaseReady} == "false" ]]; then waitUntilDatabaseIsQueryable else - echo "startNeo4j: neo4j-${NEO4J_EDITION}-${NEO4J_VERSION} already started" + echo "startNeo4j: ${neo4j_directory} already started" fi \ No newline at end of file diff --git a/scripts/stopNeo4j.sh b/scripts/stopNeo4j.sh index 2746fe0eb..bf7eda7d6 100755 --- a/scripts/stopNeo4j.sh +++ b/scripts/stopNeo4j.sh @@ -9,6 +9,7 @@ # Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands) set -o errexit -o pipefail +# Overrideable Defaults NEO4J_EDITION=${NEO4J_EDITION:-"community"} # Choose "community" or "enterprise" NEO4J_VERSION=${NEO4J_VERSION:-"5.21.2"} TOOLS_DIRECTORY=${TOOLS_DIRECTORY:-"tools"} # Get the tools directory (defaults to "tools") @@ -21,16 +22,27 @@ NEO4J_HTTP_PORT=${NEO4J_HTTP_PORT:-"7474"} SCRIPTS_DIR=${SCRIPTS_DIR:-$( CDPATH=. cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P )} # Repository directory containing the shell scripts echo "stopNeo4j: SCRIPTS_DIR=$SCRIPTS_DIR" -# Internal Constants -NEO4J_DIR="${TOOLS_DIRECTORY}/neo4j-${NEO4J_EDITION}-${NEO4J_VERSION}" -NEO4J_BIN="${NEO4J_DIR}/bin" - # Check if TOOLS_DIRECTORY variable is set if [ -z "${TOOLS_DIRECTORY}" ]; then echo "stopNeo4j: Requires variable TOOLS_DIRECTORY to be set. If it is the current directory, then use a dot to reflect that." exit 1 fi +# Detect Neo4j directory within the "tools" directory +# If the version had been updated and a local project still uses the +neo4j_directory="neo4j-${NEO4J_EDITION}-${NEO4J_VERSION}" # Default directory using environment variables +if [ ! -d "${TOOLS_DIRECTORY}/${neo4j_directory}" ] ; then + # Default directory doesn't exist. Try to find one with another version. + # This usually happens when Neo4j had been updated while a local project is still using an older version. + neo4j_directories=$(find "./${TOOLS_DIRECTORY}" -type d -name "neo4j-${NEO4J_EDITION}-*" | sort -r | head -n 1) + neo4j_directory=$(basename -- "$neo4j_directories") + echo "stopNeo4j: Auto detected Neo4j directory within the tools directory: ${neo4j_directory}" +fi + +# Internal Constants +NEO4J_DIR="${TOOLS_DIRECTORY}/${neo4j_directory}" +NEO4J_BIN="${NEO4J_DIR}/bin" + # Check if Neo4j is installed if [ -d "${NEO4J_BIN}" ] ; then echo "stopNeo4j: Using Neo4j binary directory ${NEO4J_BIN}" @@ -48,11 +60,11 @@ source "${SCRIPTS_DIR}/waitForNeo4jHttpFunctions.sh" # Check if Neo4j is stopped (not running) using a temporary NEO4J_HOME environment variable that points to the current installation isDatabaseReady=$(isDatabaseQueryable) if [[ ${isDatabaseReady} == "false" ]]; then - echo "stopNeo4j: neo4j-${NEO4J_EDITION}-${NEO4J_VERSION} already stopped" + echo "stopNeo4j: ${neo4j_directory} already stopped" exit 0 else if isWindows; then - echo "stopNeo4j: IMPORTANT on Windows: Please close the console window or stop the service manually where neo4j-${NEO4J_EDITION}-${NEO4J_VERSION} is running." + echo "stopNeo4j: IMPORTANT on Windows: Please close the console window or stop the service manually where ${neo4j_directory} is running." else # Stop Neo4j using a temporary NEO4J_HOME environment variable that points to the current installation NEO4J_HOME=${NEO4J_DIR} ${NEO4J_BIN}/neo4j stop @@ -62,10 +74,10 @@ fi # Check if Neo4j is still not running using a temporary NEO4J_HOME environment variable that points to the current installation isDatabaseReady=$(isDatabaseQueryable) if [[ ${isDatabaseReady} == "false" ]]; then - echo "stopNeo4j: Successfully stopped neo4j-${NEO4J_EDITION}-${NEO4J_VERSION}" + echo "stopNeo4j: Successfully stopped ${neo4j_directory}" else if ! isWindows; then - echo "stopNeo4j: neo4j-${NEO4J_EDITION}-${NEO4J_VERSION} still running. Something went wrong. Details see 'NEO4J_HOME=${NEO4J_DIR} ${NEO4J_BIN}/neo4j${scriptExtension} status'." + echo "stopNeo4j: ${neo4j_directory} still running. Something went wrong. Details see 'NEO4J_HOME=${NEO4J_DIR} ${NEO4J_BIN}/neo4j${scriptExtension} status'." exit 1 fi fi From 2b1eae0e2fd285ee6cbf46e3886a2d9478efc3e9 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Tue, 13 Aug 2024 08:24:16 +0200 Subject: [PATCH 02/16] Ignore .DS_Store files while scanning --- scripts/configuration/template-neo4jv4-jqassistant.yaml | 1 + scripts/configuration/template-neo4jv5-jqassistant.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/scripts/configuration/template-neo4jv4-jqassistant.yaml b/scripts/configuration/template-neo4jv4-jqassistant.yaml index 018b004da..c31732b5d 100644 --- a/scripts/configuration/template-neo4jv4-jqassistant.yaml +++ b/scripts/configuration/template-neo4jv4-jqassistant.yaml @@ -131,6 +131,7 @@ jqassistant: # -Djqassistant.scan.properties properties: # plugin.property.key: value + file.exclude: "*/.DS_Store" json.file.exclude: "*/.reports/jqa/ts-output.json" # The analyze configuration diff --git a/scripts/configuration/template-neo4jv5-jqassistant.yaml b/scripts/configuration/template-neo4jv5-jqassistant.yaml index 67474a47a..d73be1db5 100644 --- a/scripts/configuration/template-neo4jv5-jqassistant.yaml +++ b/scripts/configuration/template-neo4jv5-jqassistant.yaml @@ -131,6 +131,7 @@ jqassistant: # -Djqassistant.scan.properties properties: # plugin.property.key: value + file.exclude: "*/.DS_Store" json.file.exclude: "*/.reports/jqa/ts-output.json" # The analyze configuration From 4cb0cacf631ea2e045786c7a680e0073392ab3c9 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:11:13 +0200 Subject: [PATCH 03/16] Delete plain files in .git directory since they can't be ignored for now. Related to: https://github.com/jQAssistant/jqassistant/issues/410 --- .../Delete_plain_git_directory_file_nodes.cypher | 14 ++++++++++++++ scripts/importGit.sh | 12 ++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 cypher/GitLog/Delete_plain_git_directory_file_nodes.cypher diff --git a/cypher/GitLog/Delete_plain_git_directory_file_nodes.cypher b/cypher/GitLog/Delete_plain_git_directory_file_nodes.cypher new file mode 100644 index 000000000..f8baf7da0 --- /dev/null +++ b/cypher/GitLog/Delete_plain_git_directory_file_nodes.cypher @@ -0,0 +1,14 @@ +// Delete plain file nodes in "/.git" directory + +MATCH (git_metadata_file:File)<-[:CONTAINS*]-(git_directory:Directory) +WHERE git_directory.fileName ENDS WITH '/.git' + WITH git_directory.fileName AS gitDirectory + ,count(DISTINCT git_metadata_file.fileName) AS numberOfFiles + ,collect(DISTINCT git_metadata_file.fileName)[0..4] AS fileExamples + ,collect(DISTINCT git_metadata_file) AS git_metadata_files +UNWIND git_metadata_files AS git_metadata_file + CALL { WITH git_metadata_file + DETACH DELETE git_metadata_file + } IN TRANSACTIONS OF 1000 ROWS +RETURN DISTINCT gitDirectory, numberOfFiles, fileExamples +ORDER BY numberOfFiles DESC \ No newline at end of file diff --git a/scripts/importGit.sh b/scripts/importGit.sh index c144cc976..884d30e77 100755 --- a/scripts/importGit.sh +++ b/scripts/importGit.sh @@ -135,12 +135,24 @@ postGitLogImport() { postGitPluginImport() { echo "importGit: Creating indexes for plugin-provided git data..." + + # TODO: The deletion of all plain files in the "/.git" directory is needed + # until there is a way to exclude all files inside a directory + # while still being able to get them analyzed by the git plugin. + # This would most likely be solved with https://github.com/jQAssistant/jqassistant/issues/410 + execute_cypher "${GIT_LOG_CYPHER_DIR}/Delete_plain_git_directory_file_nodes.cypher" execute_cypher "${GIT_LOG_CYPHER_DIR}/Index_commit_sha.cypher" execute_cypher "${GIT_LOG_CYPHER_DIR}/Index_file_name.cypher" execute_cypher "${GIT_LOG_CYPHER_DIR}/Index_file_relative_path.cypher" commonPostGitImport + #TODO Add verification steps + #verificationResult=$( execute_cypher_http_number_of_lines_in_result "${GIT_LOG_CYPHER_DIR}/Verify_code_to_git_file_unambiguous.cypher" ) + #if [ "${verificationResult}" != 0 ]; then + # echo "importGit: Error: Verification failed. Git:File->!Git:File RESOLVES_TO relationships ambiguous" + #fi + echo "importGit: Add numberOfGitCommits property to nodes with matching file names..." execute_cypher "${GIT_LOG_CYPHER_DIR}/Set_number_of_git_plugin_commits.cypher" } From 9f22f7b45e42371fc4e508de7e387edfa5766744 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Sun, 18 Aug 2024 17:24:43 +0200 Subject: [PATCH 04/16] Fix git file matching ambiguity for multi repository analysis --- ...relationships_to_git_files_for_Java.cypher | 25 ++++++++++++----- ...onships_to_git_files_for_Typescript.cypher | 27 ++++++++++++++----- .../Import_aggregated_git_log_csv_data.cypher | 2 +- cypher/GitLog/Import_git_log_csv_data.cypher | 2 +- ...les_by_resolved_label_and_extension.cypher | 14 ++++++++++ ...Verify_code_to_git_file_unambiguous.cypher | 16 +++++++++++ ...Verify_git_to_code_file_unambiguous.cypher | 17 ++++++++++++ scripts/importGit.sh | 12 ++++----- 8 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 cypher/GitLog/List_git_files_by_resolved_label_and_extension.cypher create mode 100644 cypher/GitLog/Verify_code_to_git_file_unambiguous.cypher create mode 100644 cypher/GitLog/Verify_git_to_code_file_unambiguous.cypher diff --git a/cypher/GitLog/Add_RESOLVES_TO_relationships_to_git_files_for_Java.cypher b/cypher/GitLog/Add_RESOLVES_TO_relationships_to_git_files_for_Java.cypher index 353888f6f..5085f852a 100644 --- a/cypher/GitLog/Add_RESOLVES_TO_relationships_to_git_files_for_Java.cypher +++ b/cypher/GitLog/Add_RESOLVES_TO_relationships_to_git_files_for_Java.cypher @@ -1,12 +1,18 @@ -// Connect git files to code files with a RESOLVES_TO relationship if their names match -// Note: Even if is tempting to combine this file with the Typescript variant, they are intentionally spearated. +// Connect git files to Java code files with a RESOLVES_TO relationship if their names match +// Note: Its quite tricky to match Java class file paths from inside e.g. *.jar files to their source repository file path reliable. +// This could be improved by utilizing package manager data (like maven). Even that turns out to be not easy, +// since the folder structure can be customized. Therefore, this is only a simplified attempt and by no means complete. +// Note: Even if is tempting to combine this file with the Typescript variant, they are intentionally separated. // The differences are subtle but need to be thought through and tested carefully. // Having separate files makes it obvious that there needs to be one for every new source code language. -MATCH (code_file:File&!Git) -WHERE NOT EXISTS { (code_file)-[:RESOLVES_TO]->(other_file:File&!Git) } // only original nodes, no duplicates - WITH code_file, replace(code_file.fileName, '.class', '.java') AS codeFileName -MATCH (git_file:File&Git) +MATCH (code_file:!Git&File) +WHERE code_file.fileName IS NOT NULL + // Use only original code files, no resolved duplicates + AND NOT EXISTS { (code_file)-[:RESOLVES_TO]->(other_file:File&!Git) } + WITH code_file + ,replace(code_file.fileName, '.class', '.java') AS codeFileName +MATCH (git_file:Git&File) WITH * ,git_file ,coalesce(git_file.fileName, git_file.relativePath) AS gitFileName @@ -14,4 +20,9 @@ WHERE gitFileName ENDS WITH codeFileName MERGE (git_file)-[:RESOLVES_TO]->(code_file) SET git_file.resolved = true RETURN count(DISTINCT codeFileName) AS numberOfCodeFiles - ,collect(DISTINCT codeFileName + ' <-> ' + gitFileName + '\n')[0..4] AS examples \ No newline at end of file + ,collect(DISTINCT codeFileName + ' <-> ' + gitFileName + '\n')[0..4] AS examples +// RETURN codeFileName, gitFileName +// ,git_file.fileName, git_file.relativePath +// ,git_repository.fileName , code_file.absoluteFileName +// ,git_repository.name, code_file.fileName +// LIMIT 20 \ No newline at end of file diff --git a/cypher/GitLog/Add_RESOLVES_TO_relationships_to_git_files_for_Typescript.cypher b/cypher/GitLog/Add_RESOLVES_TO_relationships_to_git_files_for_Typescript.cypher index d7f8d4495..11f249d55 100644 --- a/cypher/GitLog/Add_RESOLVES_TO_relationships_to_git_files_for_Typescript.cypher +++ b/cypher/GitLog/Add_RESOLVES_TO_relationships_to_git_files_for_Typescript.cypher @@ -1,17 +1,30 @@ // Connect git files to Typescript files with a RESOLVES_TO relationship if their names match -// Note: Even if is tempting to combine this file with the Java variant, they are intentionally spearated. +// Note: Even if is tempting to combine this file with the Java variant, they are intentionally separated. // The differences are subtle but need to be thought through and tested carefully. // Having separate files makes it obvious that there needs to be one for every new source code language. -MATCH (code_file:File&!Git) -WHERE NOT EXISTS { (code_file)-[:RESOLVES_TO]->(other_file:File&!Git) } // only original nodes, no duplicates - WITH code_file, code_file.absoluteFileName AS codeFileName -MATCH (git_file:File&Git) +MATCH (code_file:!Git&File) +WHERE (code_file.absoluteFileName IS NOT NULL OR code_file.fileName IS NOT NULL) + // Use only original code files, no resolved duplicates + AND NOT EXISTS { (code_file)-[:RESOLVES_TO]->(other_file:File&!Git) } + WITH code_file + ,coalesce(code_file.absoluteFileName, code_file.fileName) AS codeFileName +MATCH (git_file:Git&File) +// Use repository if available to overcome ambiguity in multi source analysis +OPTIONAL MATCH (git_repository:Git&Repository)-[:HAS_FILE]->(git_file) WITH * + ,git_repository ,git_file - ,coalesce(git_file.fileName, git_file.relativePath) AS gitFileName + ,coalesce(coalesce(git_repository.fileName + '/', '') + git_file.fileName + ,coalesce(git_repository.name + '/', '') + git_file.relativePath + ) AS gitFileName WHERE codeFileName ENDS WITH gitFileName MERGE (git_file)-[:RESOLVES_TO]->(code_file) SET git_file.resolved = true RETURN count(DISTINCT codeFileName) AS numberOfCodeFiles - ,collect(DISTINCT codeFileName + ' <-> ' + gitFileName + '\n')[0..4] AS examples \ No newline at end of file + ,collect(DISTINCT codeFileName + ' <-> ' + gitFileName + '\n')[0..4] AS examples +// RETURN codeFileName, gitFileName +// ,git_file.fileName, git_file.relativePath +// ,git_repository.fileName , code_file.absoluteFileName +// ,git_repository.name, code_file.fileName +// LIMIT 20 \ No newline at end of file diff --git a/cypher/GitLog/Import_aggregated_git_log_csv_data.cypher b/cypher/GitLog/Import_aggregated_git_log_csv_data.cypher index b9f0de283..2db4a4956 100644 --- a/cypher/GitLog/Import_aggregated_git_log_csv_data.cypher +++ b/cypher/GitLog/Import_aggregated_git_log_csv_data.cypher @@ -9,7 +9,7 @@ CALL { WITH row month: toInteger(row.month), commits: toInteger(row.commits) }) - MERGE (git_file:Git:Log:File {fileName: row.filename}) + MERGE (git_file:Git:Log:File {fileName: row.filename, repositoryPath: $git_repository_absolute_directory_name}) MERGE (git_author)-[:AUTHORED]->(git_change_span) MERGE (git_change_span)-[:CONTAINS_CHANGED]->(git_file) MERGE (git_repository)-[:HAS_CHANGE_SPAN]->(git_change_span) diff --git a/cypher/GitLog/Import_git_log_csv_data.cypher b/cypher/GitLog/Import_git_log_csv_data.cypher index 77e6ca3a3..3bd448671 100644 --- a/cypher/GitLog/Import_git_log_csv_data.cypher +++ b/cypher/GitLog/Import_git_log_csv_data.cypher @@ -12,7 +12,7 @@ CALL { WITH row timestamp: datetime(row.timestamp), timestamp_unix: toInteger(row.timestamp_unix) }) - MERGE (git_file:Git:Log:File {fileName: row.filename}) + MERGE (git_file:Git:Log:File {fileName: row.filename, repositoryPath: $git_repository_absolute_directory_name}) MERGE (git_author)-[:AUTHORED]->(git_commit) MERGE (git_commit)-[:CONTAINS_CHANGED]->(git_file) MERGE (git_repository)-[:HAS_COMMIT]->(git_commit) diff --git a/cypher/GitLog/List_git_files_by_resolved_label_and_extension.cypher b/cypher/GitLog/List_git_files_by_resolved_label_and_extension.cypher new file mode 100644 index 000000000..f333d1f2c --- /dev/null +++ b/cypher/GitLog/List_git_files_by_resolved_label_and_extension.cypher @@ -0,0 +1,14 @@ +// List resolved and unresolved git files by their extension + +MATCH (git_file:Git&File) +OPTIONAL MATCH (git_file)-[:RESOLVES_TO]->(code_file:File&!Git) + WITH git_file + ,code_file + ,(code_file IS NOT NULL) AS resolved + ,coalesce(git_file.fileName, git_file.relativePath) AS gitFileName +RETURN resolved + ,reverse(split(split(reverse(gitFileName), '/')[0], '.')[0]) AS extension + ,count(DISTINCT git_file) AS gitFileCount + ,coalesce(labels(code_file), labels(git_file)) AS fileLabels + ,collect(DISTINCT gitFileName)[0..9] AS gitFileExamples +ORDER BY resolved ASC, gitFileCount DESC, extension ASC \ No newline at end of file diff --git a/cypher/GitLog/Verify_code_to_git_file_unambiguous.cypher b/cypher/GitLog/Verify_code_to_git_file_unambiguous.cypher new file mode 100644 index 000000000..90fa144ea --- /dev/null +++ b/cypher/GitLog/Verify_code_to_git_file_unambiguous.cypher @@ -0,0 +1,16 @@ +// Verify that code to git file relationships aren't ambiguous + +MATCH (git_file:Git&File)-[:RESOLVES_TO]->(code_file:File&!Git) + WITH code_file + ,count(*) AS numberOfResolvedGitFiles + ,collect(DISTINCT git_file) AS git_files +WHERE numberOfResolvedGitFiles > 1 +UNWIND git_files AS git_file +OPTIONAL MATCH (git_file)<-[:HAS_FILE]-(git_repository:Git&Repository) +RETURN numberOfResolvedGitFiles + ,collect(DISTINCT coalesce(code_file.absoluteFileName, code_file.fileName))[0..4] AS codeFileExamples + ,collect(DISTINCT coalesce(git_file.fileName, git_file.relativePath))[0..4] AS gitFileExamples + ,collect(DISTINCT git_repository.name)[0..4] AS gitRepositoryExamples + //,collect(git_repository)[0..9] + //,collect(git_file)[0..9] + //,collect(code_file)[0..9] \ No newline at end of file diff --git a/cypher/GitLog/Verify_git_to_code_file_unambiguous.cypher b/cypher/GitLog/Verify_git_to_code_file_unambiguous.cypher new file mode 100644 index 000000000..f4c595beb --- /dev/null +++ b/cypher/GitLog/Verify_git_to_code_file_unambiguous.cypher @@ -0,0 +1,17 @@ +// Verify that git to code file relationships aren't ambiguous + +MATCH (git_file:Git&File)-[:RESOLVES_TO]->(code_file:File&!Git) +OPTIONAL MATCH (git_file)<-[:HAS_FILE]-(git_repository:Git&Repository) + WITH git_file + ,git_repository + ,count(*) AS numberOfResolvedCodeFiles + ,collect(DISTINCT code_file) AS code_files +WHERE numberOfResolvedCodeFiles > 1 +UNWIND code_files AS code_file +RETURN numberOfResolvedCodeFiles + ,collect(DISTINCT coalesce(code_file.absoluteFileName, code_file.fileName))[0..4] AS codeFileExamples + ,collect(DISTINCT coalesce(git_file.fileName, git_file.relativePath))[0..4] AS gitFileExamples + ,collect(DISTINCT git_repository.name)[0..4] AS gitRepositoryExamples + //,collect(git_repository)[0..9] + //,collect(git_file)[0..9] + //,collect(code_file)[0..9] \ No newline at end of file diff --git a/scripts/importGit.sh b/scripts/importGit.sh index 884d30e77..44c19911f 100755 --- a/scripts/importGit.sh +++ b/scripts/importGit.sh @@ -124,6 +124,12 @@ commonPostGitImport() { echo "importGit: Creating relationships to nodes with matching file names..." execute_cypher "${GIT_LOG_CYPHER_DIR}/Add_RESOLVES_TO_relationships_to_git_files_for_Java.cypher" execute_cypher "${GIT_LOG_CYPHER_DIR}/Add_RESOLVES_TO_relationships_to_git_files_for_Typescript.cypher" + + # Since it's currently not possible to rule out ambiguity in git<->code file matching, + # the following verifications are only an additional info in the log rather than an error. + echo "importGit: Running verification queries for troubleshooting (non failing)..." + execute_cypher "${GIT_LOG_CYPHER_DIR}/Verify_git_to_code_file_unambiguous.cypher" + execute_cypher "${GIT_LOG_CYPHER_DIR}/Verify_code_to_git_file_unambiguous.cypher" } postGitLogImport() { @@ -147,12 +153,6 @@ postGitPluginImport() { commonPostGitImport - #TODO Add verification steps - #verificationResult=$( execute_cypher_http_number_of_lines_in_result "${GIT_LOG_CYPHER_DIR}/Verify_code_to_git_file_unambiguous.cypher" ) - #if [ "${verificationResult}" != 0 ]; then - # echo "importGit: Error: Verification failed. Git:File->!Git:File RESOLVES_TO relationships ambiguous" - #fi - echo "importGit: Add numberOfGitCommits property to nodes with matching file names..." execute_cypher "${GIT_LOG_CYPHER_DIR}/Set_number_of_git_plugin_commits.cypher" } From 5fd5a69311e16efb555f46a81daead3b0ff4170f Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Sun, 18 Aug 2024 17:26:12 +0200 Subject: [PATCH 05/16] Add further Axon Framework artifacts to analyze --- scripts/downloader/downloadAxonFramework.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/downloader/downloadAxonFramework.sh b/scripts/downloader/downloadAxonFramework.sh index 92f437727..00277b43c 100755 --- a/scripts/downloader/downloadAxonFramework.sh +++ b/scripts/downloader/downloadAxonFramework.sh @@ -49,6 +49,9 @@ source "${SCRIPTS_DIR}/downloadMavenArtifact.sh" -g "${ARTIFACTS_GROUP}" -a "axo source "${SCRIPTS_DIR}/downloadMavenArtifact.sh" -g "${ARTIFACTS_GROUP}" -a "axon-messaging" -v "${ARTIFACTS_VERSION}" || exit 2 source "${SCRIPTS_DIR}/downloadMavenArtifact.sh" -g "${ARTIFACTS_GROUP}" -a "axon-modelling" -v "${ARTIFACTS_VERSION}" || exit 2 source "${SCRIPTS_DIR}/downloadMavenArtifact.sh" -g "${ARTIFACTS_GROUP}" -a "axon-test" -v "${ARTIFACTS_VERSION}" || exit 2 +source "${SCRIPTS_DIR}/downloadMavenArtifact.sh" -g "${ARTIFACTS_GROUP}" -a "axon-server-connector" -v "${ARTIFACTS_VERSION}" || exit 2 +source "${SCRIPTS_DIR}/downloadMavenArtifact.sh" -g "${ARTIFACTS_GROUP}" -a "axon-spring-boot-autoconfigure" -v "${ARTIFACTS_VERSION}" || exit 2 +source "${SCRIPTS_DIR}/downloadMavenArtifact.sh" -g "${ARTIFACTS_GROUP}" -a "axon-tracing-opentelemetry" -v "${ARTIFACTS_VERSION}" || exit 2 # Download the git history (bare clone without working tree) into the "source" folder. # This makes it possible to additionally import the git log into the graph From 81861769474c0136a7f3bafd93b07de2c2ec1b1f Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Thu, 15 Aug 2024 19:58:42 +0200 Subject: [PATCH 06/16] Resolve external modules to internal when file extensions match --- ...Add_DEPENDS_ON_relationship_to_resolved_modules.cypher | 4 ++-- ...d_RESOLVES_TO_relationship_for_matching_modules.cypher | 8 +++++--- cypher/Typescript_Enrichment/Index_module_name.cypher | 3 +++ scripts/prepareAnalysis.sh | 1 + 4 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 cypher/Typescript_Enrichment/Index_module_name.cypher diff --git a/cypher/Typescript_Enrichment/Add_DEPENDS_ON_relationship_to_resolved_modules.cypher b/cypher/Typescript_Enrichment/Add_DEPENDS_ON_relationship_to_resolved_modules.cypher index 8c9c25492..84e304fdb 100644 --- a/cypher/Typescript_Enrichment/Add_DEPENDS_ON_relationship_to_resolved_modules.cypher +++ b/cypher/Typescript_Enrichment/Add_DEPENDS_ON_relationship_to_resolved_modules.cypher @@ -6,7 +6,7 @@ WHERE module <> resolvedModule CALL { WITH module, dependsOn, resolvedModule MERGE (module)-[resolvedDependsOn:DEPENDS_ON]->(resolvedModule) - SET resolvedDependsOn = dependsOn - ,resolvedDependsOn.resolved=true + ON CREATE SET resolvedDependsOn = dependsOn + ,resolvedDependsOn.resolved = true } IN TRANSACTIONS RETURN count(*) as resolvedDependencies \ No newline at end of file diff --git a/cypher/Typescript_Enrichment/Add_RESOLVES_TO_relationship_for_matching_modules.cypher b/cypher/Typescript_Enrichment/Add_RESOLVES_TO_relationship_for_matching_modules.cypher index e548fc55f..d2cb79bdd 100644 --- a/cypher/Typescript_Enrichment/Add_RESOLVES_TO_relationship_for_matching_modules.cypher +++ b/cypher/Typescript_Enrichment/Add_RESOLVES_TO_relationship_for_matching_modules.cypher @@ -8,9 +8,11 @@ WHERE module.globalFqn IS NOT NULL MATCH (externalModule:TS:ExternalModule) WHERE module.globalFqn IS NOT NULL AND (module.globalFqn = externalModule.globalFqn - OR module.globalFqn = split(externalModule.globalFqn, '/index.')[0] - OR externalModule.globalFqn = split(module.globalFqn, '/index.')[0] - OR (externalModule.name = module.name AND externalModule.namespace = module.namespace) + OR ( externalModule.name = module.name + AND externalModule.moduleName = module.moduleName + AND externalModule.namespace = module.namespace + AND externalModule.extensionExtended = module.extensionExtended + ) ) AND module <> externalModule CALL { WITH module, externalModule diff --git a/cypher/Typescript_Enrichment/Index_module_name.cypher b/cypher/Typescript_Enrichment/Index_module_name.cypher new file mode 100644 index 000000000..faff094ba --- /dev/null +++ b/cypher/Typescript_Enrichment/Index_module_name.cypher @@ -0,0 +1,3 @@ +// Create index for module name + +CREATE INDEX INDEX_MODULE_NAME IF NOT EXISTS FOR (module:TS) ON (module.moduleName) \ No newline at end of file diff --git a/scripts/prepareAnalysis.sh b/scripts/prepareAnalysis.sh index a654df218..6749599c8 100644 --- a/scripts/prepareAnalysis.sh +++ b/scripts/prepareAnalysis.sh @@ -53,6 +53,7 @@ execute_cypher "${CYPHER_DIR}/Create_Typescript_index_for_full_qualified_name.cy execute_cypher "${CYPHER_DIR}/Create_Typescript_index_for_name.cypher" # Preparation - Enrich Graph for Typescript by adding "module" and "name" properties +execute_cypher "${TYPESCRIPT_CYPHER_DIR}/Index_module_name.cypher" execute_cypher "${TYPESCRIPT_CYPHER_DIR}/Add_module_properties.cypher" # Preparation - Enrich Graph for Typescript by adding a name properties From 3a0ff20fa1828cc1788dd643ac60bd756339d64c Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Thu, 15 Aug 2024 21:38:37 +0200 Subject: [PATCH 07/16] Add progress and improve performance of change detection --- scripts/detectChangedArtifacts.sh | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/scripts/detectChangedArtifacts.sh b/scripts/detectChangedArtifacts.sh index 15b7d9d66..22d7b550b 100755 --- a/scripts/detectChangedArtifacts.sh +++ b/scripts/detectChangedArtifacts.sh @@ -70,7 +70,7 @@ get_file_size() { } # Function to process a single path -unwind_directories() { +file_names_and_sizes() { if [ -d "$1" ]; then # If it's a directory, list all files inside # except for "node_modules", "target", "temp" and the change detection file itself @@ -80,10 +80,11 @@ unwind_directories() { -type d -name "temp" -prune -o \ -not -name "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE}" \ -type f \ + -exec stat -f "%N %z" {} + \ | sort elif [ -f "$1" ]; then # If it's a file, just echo the file path - echo "$1" + stat -f "%N %z" < "$1" fi } @@ -92,17 +93,18 @@ unwind_directories() { # and calculates the md5 hash for every of these . get_md5_checksum_of_all_file_names_and_sizes() { local paths=${1} - local files_and_sizes="" + local total_paths; total_paths=$(echo "${paths}" | awk -F '[\t,]' '{print NF}') + local all_files_and_sizes="" + local processed_paths=0 for path in ${paths//,/ }; do - files=$(unwind_directories "${path}") - for file in ${files}; do - size=$(get_file_size "${file}") - files_and_sizes="${files_and_sizes}${file}${size}" - done + local files_and_their_size; files_and_their_size=$(file_names_and_sizes "${path}") + all_files_and_sizes="${all_files_and_sizes}${files_and_their_size}" + processed_paths=$((processed_paths + 1)) + echo -ne "detectChangedArtifacts: Calculate checksum progress: ($processed_paths/$total_paths)\r" >&2 done - - echo "${files_and_sizes}" | openssl md5 | awk '{print $2}' + echo "" >&2 + echo "${all_files_and_sizes}" | openssl md5 | awk '{print $2}' } # Use find to list all files in the directory with their properties, @@ -118,6 +120,8 @@ if [ ! -f "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE_PATH}" ] ; then mkdir -p "${ARTIFACTS_DIRECTORY}" echo "${CURRENT_ARTIFACTS_HASH}" > "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE_PATH}" echo "detectChangedArtifacts: Change detection file created" >&2 + else + echo "detectChangedArtifacts: Skipping file creation with content (=hash) ${CURRENT_ARTIFACTS_HASH}" >&2 fi echo 1 # 1=Change detected and change detection file created exit 0 @@ -132,6 +136,8 @@ else # Write the updated hash into the file containing the hash of the files list for the next call echo "$CURRENT_ARTIFACTS_HASH" > "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE_PATH}" echo "detectChangedArtifacts: Change detection file updated" >&2 + else + echo "detectChangedArtifacts: Skipping file update with content (=hash) ${CURRENT_ARTIFACTS_HASH}" >&2 fi echo 2 # 2=Change detected and change detection file updated fi \ No newline at end of file From 6da4f8379891dae7effcf1aef214be9ef31620c5 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:51:48 +0200 Subject: [PATCH 08/16] Improve performance by only searching relevant paths --- scripts/findPathsToScan.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/scripts/findPathsToScan.sh b/scripts/findPathsToScan.sh index bd1bf97d5..9b1bc7641 100755 --- a/scripts/findPathsToScan.sh +++ b/scripts/findPathsToScan.sh @@ -45,11 +45,17 @@ if [ -d "./${SOURCE_DIRECTORY}" ] ; then echo "findPathsToScan: Scanning Typescript source using @jqassistant/ts-lce..." >&2 ( cd "./${SOURCE_DIRECTORY}" && npx --yes @jqassistant/ts-lce@1.2.1 --extension React >"./../runtime/logs/jqassistant-typescript-scan.log" 2>&1 || exit ) else - echo "findPathToScan Error: Command npx not found. It's needed to execute @jqassistant/ts-lce to scan Typescript projects." + echo "findPathToScan Error: Command npx not found. It's needed to execute @jqassistant/ts-lce to scan Typescript projects." >&2 fi # Scan Typescript analysis json data files in the source directory - typescriptAnalysisFiles="$(find "./${SOURCE_DIRECTORY}" -type f -path "*/.reports/jqa/ts-output.json" -exec echo typescript:project::{} \; | tr '\n' ',' | sed 's/,$/\n/')" + typescriptAnalysisFiles="$(find "./${SOURCE_DIRECTORY}" \ + -type d -name "node_modules" -prune -o \ + -type d -name "target" -prune -o \ + -type d -name "temp" -prune -o \ + -type f -path "*/.reports/jqa/ts-output.json" \ + -exec echo typescript:project::{} \; | tr '\n' ',' | sed 's/,$/\n/')" + if [ -n "${typescriptAnalysisFiles}" ]; then directoriesAndFilesToScan="$(appendNonEmpty "${directoriesAndFilesToScan}")${typescriptAnalysisFiles}" fi @@ -64,7 +70,11 @@ if [ -d "./${SOURCE_DIRECTORY}" ] ; then # Scan git repositories in the artifacts directory if [ "${IMPORT_GIT_LOG_DATA_IF_SOURCE_IS_PRESENT}" = "" ] || [ "${IMPORT_GIT_LOG_DATA_IF_SOURCE_IS_PRESENT}" = "plugin" ] ; then - gitDirectories="$(find "./${SOURCE_DIRECTORY}" -type d -name ".git" -exec echo {} \; | tr '\n' ',' | sed 's/,$/\n/')" + gitDirectories="$(find "./${SOURCE_DIRECTORY}" \ + -type d -name "node_modules" -prune -o \ + -type d -name "target" -prune -o \ + -type d -name "temp" -prune -o \ + -type d -name ".git" -exec echo {} \; | tr '\n' ',' | sed 's/,$/\n/')" if [ -n "${gitDirectories}" ]; then directoriesAndFilesToScan="$(appendNonEmpty "${directoriesAndFilesToScan}")${gitDirectories}" fi From 20ffe20eaafe82c8c9315271be2c2b3528bfc51d Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:13:02 +0200 Subject: [PATCH 09/16] Copy Typescript scanning log to console and file --- scripts/findPathsToScan.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/findPathsToScan.sh b/scripts/findPathsToScan.sh index 9b1bc7641..1830891a1 100755 --- a/scripts/findPathsToScan.sh +++ b/scripts/findPathsToScan.sh @@ -43,7 +43,13 @@ if [ -d "./${SOURCE_DIRECTORY}" ] ; then # TODO: Remove patchJQAssistantTypescriptPlugin when issue is resolved: https://github.com/jqassistant-plugin/jqassistant-typescript-plugin/issues/125 source "${SCRIPTS_DIR}/patchJQAssistantTypescriptPlugin.sh" >&2 echo "findPathsToScan: Scanning Typescript source using @jqassistant/ts-lce..." >&2 - ( cd "./${SOURCE_DIRECTORY}" && npx --yes @jqassistant/ts-lce@1.2.1 --extension React >"./../runtime/logs/jqassistant-typescript-scan.log" 2>&1 || exit ) + # Note: The npx command will be executed in the source directory using a subshell by putting parentheses around it. + # The subshell is the reason why it isn't needed to change back to the main directory after execution. + # Note: This script must not output anything except for the return code to stdout, + # all output of the scanning needs to be redirected to stderr using ">&2". + # For later troubleshooting, the output is also copied to a dedicated log file using "tee". + # Note: Don't worry about the hardcoded version number. It will be updated by Renovate using a custom Manager. + ( cd "./${SOURCE_DIRECTORY}" && npx --yes @jqassistant/ts-lce@1.2.1 --extension React 2>&1 | tee "./../runtime/logs/jqassistant-typescript-scan.log" >&2 || exit ) else echo "findPathToScan Error: Command npx not found. It's needed to execute @jqassistant/ts-lce to scan Typescript projects." >&2 fi From 7c03d491d91a9b407d138be89afcddb01ecea9a2 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:53:04 +0200 Subject: [PATCH 10/16] Add data verification before projection creation. --- .../Dependencies_0_Verify_Projectable.cypher | 33 +++++++++++ scripts/projectionFunctions.sh | 57 ++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 cypher/Dependencies_Projection/Dependencies_0_Verify_Projectable.cypher diff --git a/cypher/Dependencies_Projection/Dependencies_0_Verify_Projectable.cypher b/cypher/Dependencies_Projection/Dependencies_0_Verify_Projectable.cypher new file mode 100644 index 000000000..43523cfec --- /dev/null +++ b/cypher/Dependencies_Projection/Dependencies_0_Verify_Projectable.cypher @@ -0,0 +1,33 @@ +// Verify that nodes and relationships are complete and ready for projection + + MATCH (source:TS:Module)-[dependency:DEPENDS_ON]->(target:Module) + WHERE $dependencies_projection_node IN labels(source) + AND $dependencies_projection_node IN labels(target) + WITH (NOT $dependencies_projection_weight_property IN keys(dependency)) AS missingWeightProperty + ,(dependency[$dependencies_projection_weight_property]) AS weightPropertyValue + ,(dependency[$dependencies_projection_weight_property] < 1) AS nonPositiveWeightPropertyValue + ,coalesce(dependency.resolved, false) AS resolvedDependency + ,EXISTS { (target)<-[:RESOLVES_TO]-(resolvedTarget:ExternalModule) } AS resolvedTarget + ,(source.incomingDependencies IS NULL OR + target.incomingDependencies IS NULL) AS missingIncomingDependencies + ,(source.outgoingDependencies IS NULL OR + target.outgoingDependencies IS NULL) AS missingOutgoingDependencies + ,source + ,target + WHERE missingWeightProperty +// OR nonPositiveWeightPropertyValue // if strict positive weights are needed + OR missingIncomingDependencies + OR missingOutgoingDependencies +RETURN missingWeightProperty + ,nonPositiveWeightPropertyValue + ,resolvedDependency + ,resolvedTarget + ,missingIncomingDependencies + ,missingOutgoingDependencies + ,count(*) AS numberOfRelationships + ,min(weightPropertyValue) AS minWeightPropertyValue + ,max(weightPropertyValue) AS maxWeightPropertyValue + ,collect(DISTINCT source.globalFqn + ' -> ' + target.globalFqn)[0..4] AS examples + // Output source and target nodes for troubleshooting + //,collect(source)[0..4] + //,collect(target)[0..4] \ No newline at end of file diff --git a/scripts/projectionFunctions.sh b/scripts/projectionFunctions.sh index 2f5106170..8403b9230 100644 --- a/scripts/projectionFunctions.sh +++ b/scripts/projectionFunctions.sh @@ -64,6 +64,54 @@ logNoDataForProjection() { echo "projectionFunctions: $(date +'%Y-%m-%dT%H:%M:%S%z') No data. ${programmingLanguage} ${projectionName} analysis skipped." } +# Writes a log entry for the case when data validation failed. +# +# Required Parameters: +# - dependencies_projection=... +# Name prefix for the in-memory projection name for dependencies. Example: "type-centrality" +# - dependencies_projection_language=... +# Optional name of the associated programming language for logging details. Default: "Java". Example: "Typescript" +# - verificationResult=... +# Full query result of the failed verification +logDataVerificationFailedForProjection() { + local programmingLanguage projectionName verificationResult + programmingLanguage=$( extractQueryParameter "dependencies_projection_language" "${@}" ) + programmingLanguage=${programmingLanguage:-"Java"} # Set to default value "Java" if not set since it is optional + projectionName=$( extractQueryParameter "dependencies_projection" "${@}" ) + redColor='\033[0;31m' + noColor='\033[0m' + echo -e "${redColor}projectionFunctions: $(date +'%Y-%m-%dT%H:%M:%S%z') Error. Data validation for ${programmingLanguage} ${projectionName} failed.${noColor}" >&2 + echo -e "${redColor} -> This might be due to missing node or relationship properties.${noColor}" >&2 + echo -e "${redColor} For more details see the following verification query and its result (CSV):${noColor}" >&2 +} + +# Verifies if the nodes and relationships properties are complete (no missing properties) and the data is ready for projection. +# +# Returns true (=0) if the data is ready for projection. +# Returns false (=1) if the verification failed. +# Exits with an error if there are technical issues. +# +# Required Parameters: +# - dependencies_projection=... +# Name prefix for the in-memory projection name for dependencies. Example: "type-centrality" +# - dependencies_projection_node=... +# Label of the nodes that will be used for the projection. Example: "Type" +# - dependencies_projection_weight_property=... +# Name of the node property that contains the dependency weight. Example: "weight" +# - dependencies_projection_language=... +# Optional name of the associated programming language for logging details. Default: "Java". Example: "Typescript" +verifyDataReadyForProjection() { + local verificationResult + verificationResult=$( execute_cypher "${PROJECTION_CYPHER_DIR}/Dependencies_0_Verify_Projectable.cypher" "${@}") + if is_csv_column_greater_zero "${verificationResult}" "numberOfRelationships"; then + logDataVerificationFailedForProjection "${@}" "verificationResult=${verificationResult}" + redColor='\033[0;31m' + noColor='\033[0m' + echo -e "${redColor}${verificationResult}${noColor}" >&2 + exit 1; + fi +} + # Creates a directed Graph projection for dependencies between nodes specified by the parameter "dependencies_projection_node". # Nodes without incoming and outgoing dependencies will be filtered out using a subgraph. # @@ -82,9 +130,10 @@ logNoDataForProjection() { # Optional name of the associated programming language for logging details. Default: "Java". Example: "Typescript" createDirectedDependencyProjection() { logProjectionCreationStart "${@}" + verifyDataReadyForProjection "${@}" projectionCheckResult=$( execute_cypher_http_number_of_lines_in_result "${PROJECTION_CYPHER_DIR}/Dependencies_0_Check_Projectable.cypher" "${@}" ) - if [[ "${projectionCheckResult}" -lt 1 ]]; then + if [ "${projectionCheckResult}" -lt 1 ]; then logNoDataForProjection "${@}" return 1 fi @@ -120,9 +169,10 @@ createDirectedDependencyProjection() { # Optional name of the associated programming language for logging details. Default: "Java". Example: "Typescript" createUndirectedDependencyProjection() { logProjectionCreationStart "${@}" + verifyDataReadyForProjection "${@}" projectionCheckResult=$( execute_cypher_http_number_of_lines_in_result "${PROJECTION_CYPHER_DIR}/Dependencies_0_Check_Projectable.cypher" "${@}" ) - if [[ "${projectionCheckResult}" -lt 1 ]]; then + if [ "${projectionCheckResult}" -lt 1 ]; then return 1 fi execute_cypher "${PROJECTION_CYPHER_DIR}/Dependencies_1_Delete_Projection.cypher" "${@}" >/dev/null @@ -153,6 +203,7 @@ createUndirectedDependencyProjection() { # Optional name of the associated programming language for logging details. Default: "Java". Example: "Typescript" createDirectedJavaTypeDependencyProjection() { logProjectionCreationStart "${@}" + verifyDataReadyForProjection "${@}" "dependencies_projection_node=Type" "dependencies_projection_weight_property=weight" execute_cypher "${PROJECTION_CYPHER_DIR}/Dependencies_2_Delete_Subgraph.cypher" "${@}" >/dev/null @@ -180,6 +231,7 @@ createDirectedJavaTypeDependencyProjection() { # Optional name of the associated programming language for logging details. Default: "Java". Example: "Typescript" createUndirectedJavaTypeDependencyProjection() { logProjectionCreationStart "${@}" + verifyDataReadyForProjection "${@}" "dependencies_projection_node=Type" "dependencies_projection_weight_property=weight" execute_cypher "${PROJECTION_CYPHER_DIR}/Dependencies_2_Delete_Subgraph.cypher" "${@}" >/dev/null @@ -207,6 +259,7 @@ createUndirectedJavaTypeDependencyProjection() { # Optional name of the associated programming language for logging details. Default: "Java". Example: "Typescript" createDirectedJavaMethodDependencyProjection() { logProjectionCreationStart "${@}" + verifyDataReadyForProjection "${@}" "dependencies_projection_node=Method" "dependencies_projection_weight_property=weight" execute_cypher "${PROJECTION_CYPHER_DIR}/Dependencies_2_Delete_Subgraph.cypher" "${@}" >/dev/null From 4333805ad3b222b529e5a794ba244c3928831940 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Sun, 18 Aug 2024 08:14:11 +0200 Subject: [PATCH 11/16] Rename to more general detectChangedFiles --- ...ngedArtifacts.sh => detectChangedFiles.sh} | 41 +++++++++++-------- scripts/resetAndScanChanged.sh | 8 ++-- 2 files changed, 27 insertions(+), 22 deletions(-) rename scripts/{detectChangedArtifacts.sh => detectChangedFiles.sh} (69%) diff --git a/scripts/detectChangedArtifacts.sh b/scripts/detectChangedFiles.sh similarity index 69% rename from scripts/detectChangedArtifacts.sh rename to scripts/detectChangedFiles.sh index 22d7b550b..68a45a8cd 100755 --- a/scripts/detectChangedArtifacts.sh +++ b/scripts/detectChangedFiles.sh @@ -16,12 +16,13 @@ set -o errexit -o pipefail # Overrideable defaults ARTIFACTS_DIRECTORY=${ARTIFACTS_DIRECTORY:-"artifacts"} -ARTIFACTS_CHANGE_DETECTION_HASH_FILE=${ARTIFACTS_CHANGE_DETECTION_HASH_FILE:-"artifactsChangeDetectionHash.txt"} # Name of the file that contains the hash code of the file list for change detection -ARTIFACTS_CHANGE_DETECTION_HASH_FILE_PATH="./${ARTIFACTS_DIRECTORY}/$ARTIFACTS_CHANGE_DETECTION_HASH_FILE" +ARTIFACTS_CHANGE_DETECTION_HASH_FILE=${ARTIFACTS_CHANGE_DETECTION_HASH_FILE:-"artifactsChangeDetectionHash.txt"} # !DEPRECATED! Use CHANGE_DETECTION_HASH_FILE. +CHANGE_DETECTION_HASH_FILE=${CHANGE_DETECTION_HASH_FILE:-"${ARTIFACTS_CHANGE_DETECTION_HASH_FILE}"} # Name of the file that contains the hash code of the file list for change detection +CHANGE_DETECTION_HASH_FILE_PATH=${CHANGE_DETECTION_HASH_FILE_PATH:-"./${ARTIFACTS_DIRECTORY}/${CHANGE_DETECTION_HASH_FILE}"} # Path of the file that contains the hash code of the file list for change detection # Function to display script usage usage() { - echo "Usage: $0 [--readonly] [--paths ]" + echo "Usage: $0 [--readonly] [--paths (default=artifacts)]" exit 1 } @@ -43,7 +44,7 @@ while [[ $# -gt 0 ]]; do shift ;; *) - echo "detectChangedArtifacts: Error: Unknown option: ${key}" + echo "detectChangedFiles: Error: Unknown option: ${key}" usage ;; esac @@ -51,10 +52,12 @@ while [[ $# -gt 0 ]]; do done if ${readonlyMode}; then - echo "detectChangedArtifacts: Readonly mode activated. Change detection file won't be created." >&2 + echo "detectChangedFiles: Readonly mode activated. Change detection file won't be created." >&2 +else + echo "detectChangedFiles: ${CHANGE_DETECTION_HASH_FILE_PATH} will be used as change detection file." >&2 fi -# Check if the artifacts directory exists +# Check if the paths parameter exist if [ -z "${paths}" ] ; then echo 0 # 0=No change detected. The path list is empty. There is nothing to compare. Therefore assume that there are no changes. exit 0 @@ -78,7 +81,7 @@ file_names_and_sizes() { -type d -name "node_modules" -prune -o \ -type d -name "target" -prune -o \ -type d -name "temp" -prune -o \ - -not -name "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE}" \ + -not -path "${CHANGE_DETECTION_HASH_FILE_PATH}" \ -type f \ -exec stat -f "%N %z" {} + \ | sort @@ -101,7 +104,7 @@ get_md5_checksum_of_all_file_names_and_sizes() { local files_and_their_size; files_and_their_size=$(file_names_and_sizes "${path}") all_files_and_sizes="${all_files_and_sizes}${files_and_their_size}" processed_paths=$((processed_paths + 1)) - echo -ne "detectChangedArtifacts: Calculate checksum progress: ($processed_paths/$total_paths)\r" >&2 + echo -ne "detectChangedFiles: Calculate checksum progress: ($processed_paths/$total_paths)\r" >&2 done echo "" >&2 echo "${all_files_and_sizes}" | openssl md5 | awk '{print $2}' @@ -111,17 +114,19 @@ get_md5_checksum_of_all_file_names_and_sizes() { # sort the output, and pipe it to md5 to create a hash # Use openssl md5 that is at least available on Mac and Linux. # See: https://github.com/TRON-US/go-btfs/issues/90#issuecomment-517409369 -CURRENT_ARTIFACTS_HASH=$(get_md5_checksum_of_all_file_names_and_sizes "${paths}") +CURRENT_FILES_HASH=$(get_md5_checksum_of_all_file_names_and_sizes "${paths}") # Assume that the files where changed if the file containing the hash of the file list does not exist yet. -if [ ! -f "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE_PATH}" ] ; then +if [ ! -f "${CHANGE_DETECTION_HASH_FILE_PATH}" ] ; then if [ "${readonlyMode}" = false ] ; then + # Create the directory for the hash file if it hadn't existed yet. + hash_file_directory=$(dirname "${CHANGE_DETECTION_HASH_FILE_PATH}") + mkdir -p "${hash_file_directory}" # Create the file containing the hash of the files list to a new file for the next call - mkdir -p "${ARTIFACTS_DIRECTORY}" - echo "${CURRENT_ARTIFACTS_HASH}" > "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE_PATH}" - echo "detectChangedArtifacts: Change detection file created" >&2 + echo "${CURRENT_FILES_HASH}" > "${CHANGE_DETECTION_HASH_FILE_PATH}" + echo "detectChangedFiles: Change detection file created" >&2 else - echo "detectChangedArtifacts: Skipping file creation with content (=hash) ${CURRENT_ARTIFACTS_HASH}" >&2 + echo "detectChangedFiles: Skipping file creation with content (=hash) ${CURRENT_FILES_HASH}" >&2 fi echo 1 # 1=Change detected and change detection file created exit 0 @@ -129,15 +134,15 @@ fi # Assume that there is no change if the saved hash is equal to the current one. # Otherwise assume that the files where changed and overwrite the hash with the current one for the next call -if [[ $(< "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE_PATH}") == "$CURRENT_ARTIFACTS_HASH" ]] ; then +if [[ $(< "${CHANGE_DETECTION_HASH_FILE_PATH}") == "$CURRENT_FILES_HASH" ]] ; then echo 0 # 0=No change detected else if ! ${readonlyMode}; then # Write the updated hash into the file containing the hash of the files list for the next call - echo "$CURRENT_ARTIFACTS_HASH" > "${ARTIFACTS_CHANGE_DETECTION_HASH_FILE_PATH}" - echo "detectChangedArtifacts: Change detection file updated" >&2 + echo "$CURRENT_FILES_HASH" > "${CHANGE_DETECTION_HASH_FILE_PATH}" + echo "detectChangedFiles: Change detection file updated" >&2 else - echo "detectChangedArtifacts: Skipping file update with content (=hash) ${CURRENT_ARTIFACTS_HASH}" >&2 + echo "detectChangedFiles: Skipping file update with content (=hash) ${CURRENT_FILES_HASH}" >&2 fi echo 2 # 2=Change detected and change detection file updated fi \ No newline at end of file diff --git a/scripts/resetAndScanChanged.sh b/scripts/resetAndScanChanged.sh index 04a69a3e0..43c409e7a 100755 --- a/scripts/resetAndScanChanged.sh +++ b/scripts/resetAndScanChanged.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# Executes "resetAndScan.sh" only if "detectChangedArtifacts.sh" returns detected changes. +# Executes "resetAndScan.sh" only if "detectChangedFiles.sh" returns detected changes. # Note: "resetAndScan" expects jQAssistant to be installed in the "tools" directory. -# Requires resetAndScan.sh, copyPackageJsonFiles.sh, detectChangedArtifacts.sh, findPathsToScan.sh +# Requires resetAndScan.sh, copyPackageJsonFiles.sh, detectChangedFiles.sh, findPathsToScan.sh # Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands) set -o errexit -o pipefail @@ -24,11 +24,11 @@ filesAndDirectoriesToScan=$( source "${SCRIPTS_DIR}/findPathsToScan.sh" ) source "${SCRIPTS_DIR}/copyPackageJsonFiles.sh" # Scan and analyze Artifacts when they were changed -changeDetectionReturnCode=$( source "${SCRIPTS_DIR}/detectChangedArtifacts.sh" --readonly --paths "${filesAndDirectoriesToScan}") +changeDetectionReturnCode=$( source "${SCRIPTS_DIR}/detectChangedFiles.sh" --readonly --paths "${filesAndDirectoriesToScan}") if [[ "${changeDetectionReturnCode}" == "0" ]] ; then echo "resetAndScanChanged: Artifacts unchanged. Scan skipped." else echo "resetAndScanChanged: Detected change (${changeDetectionReturnCode}). Resetting database and scanning artifacts." source "${SCRIPTS_DIR}/resetAndScan.sh" "${filesAndDirectoriesToScan}" - changeDetectionReturnCode=$( source "${SCRIPTS_DIR}/detectChangedArtifacts.sh" --paths "${filesAndDirectoriesToScan}") + changeDetectionReturnCode=$( source "${SCRIPTS_DIR}/detectChangedFiles.sh" --paths "${filesAndDirectoriesToScan}") fi \ No newline at end of file From c0e290d2676f8d83a7414ac1f7ba6a49e05a08b0 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Sun, 18 Aug 2024 11:20:57 +0200 Subject: [PATCH 12/16] Add custom hashfile path option to change detection --- scripts/detectChangedFiles.sh | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/scripts/detectChangedFiles.sh b/scripts/detectChangedFiles.sh index 68a45a8cd..4193c5252 100755 --- a/scripts/detectChangedFiles.sh +++ b/scripts/detectChangedFiles.sh @@ -18,17 +18,20 @@ set -o errexit -o pipefail ARTIFACTS_DIRECTORY=${ARTIFACTS_DIRECTORY:-"artifacts"} ARTIFACTS_CHANGE_DETECTION_HASH_FILE=${ARTIFACTS_CHANGE_DETECTION_HASH_FILE:-"artifactsChangeDetectionHash.txt"} # !DEPRECATED! Use CHANGE_DETECTION_HASH_FILE. CHANGE_DETECTION_HASH_FILE=${CHANGE_DETECTION_HASH_FILE:-"${ARTIFACTS_CHANGE_DETECTION_HASH_FILE}"} # Name of the file that contains the hash code of the file list for change detection -CHANGE_DETECTION_HASH_FILE_PATH=${CHANGE_DETECTION_HASH_FILE_PATH:-"./${ARTIFACTS_DIRECTORY}/${CHANGE_DETECTION_HASH_FILE}"} # Path of the file that contains the hash code of the file list for change detection +CHANGE_DETECTION_HASH_FILE_PATH=${CHANGE_DETECTION_HASH_FILE_PATH:-"./${ARTIFACTS_DIRECTORY}/${CHANGE_DETECTION_HASH_FILE}"} # Default path of the file that contains the hash code of the file list for change detection. Can be overridden by a command line option. # Function to display script usage usage() { - echo "Usage: $0 [--readonly] [--paths (default=artifacts)]" + echo "Usage: $0 [--readonly]" + echo " [--paths (default=artifacts)]" + echo " [--hashfile (default=env var CHANGE_DETECTION_HASH_FILE_PATH)]" exit 1 } # Default values readonlyMode=false paths="./${ARTIFACTS_DIRECTORY}" +hashFilePath="${CHANGE_DETECTION_HASH_FILE_PATH}" # Parse command line arguments while [[ $# -gt 0 ]]; do @@ -43,6 +46,10 @@ while [[ $# -gt 0 ]]; do paths="${value}" shift ;; + --hashfile) + hashFilePath="${value}" + shift + ;; *) echo "detectChangedFiles: Error: Unknown option: ${key}" usage @@ -54,7 +61,7 @@ done if ${readonlyMode}; then echo "detectChangedFiles: Readonly mode activated. Change detection file won't be created." >&2 else - echo "detectChangedFiles: ${CHANGE_DETECTION_HASH_FILE_PATH} will be used as change detection file." >&2 + echo "detectChangedFiles: ${hashFilePath} will be used as change detection file." >&2 fi # Check if the paths parameter exist @@ -81,7 +88,7 @@ file_names_and_sizes() { -type d -name "node_modules" -prune -o \ -type d -name "target" -prune -o \ -type d -name "temp" -prune -o \ - -not -path "${CHANGE_DETECTION_HASH_FILE_PATH}" \ + -not -path "${hashFilePath}" \ -type f \ -exec stat -f "%N %z" {} + \ | sort @@ -117,13 +124,13 @@ get_md5_checksum_of_all_file_names_and_sizes() { CURRENT_FILES_HASH=$(get_md5_checksum_of_all_file_names_and_sizes "${paths}") # Assume that the files where changed if the file containing the hash of the file list does not exist yet. -if [ ! -f "${CHANGE_DETECTION_HASH_FILE_PATH}" ] ; then +if [ ! -f "${hashFilePath}" ] ; then if [ "${readonlyMode}" = false ] ; then # Create the directory for the hash file if it hadn't existed yet. - hash_file_directory=$(dirname "${CHANGE_DETECTION_HASH_FILE_PATH}") + hash_file_directory=$(dirname "${hashFilePath}") mkdir -p "${hash_file_directory}" # Create the file containing the hash of the files list to a new file for the next call - echo "${CURRENT_FILES_HASH}" > "${CHANGE_DETECTION_HASH_FILE_PATH}" + echo "${CURRENT_FILES_HASH}" > "${hashFilePath}" echo "detectChangedFiles: Change detection file created" >&2 else echo "detectChangedFiles: Skipping file creation with content (=hash) ${CURRENT_FILES_HASH}" >&2 @@ -134,12 +141,12 @@ fi # Assume that there is no change if the saved hash is equal to the current one. # Otherwise assume that the files where changed and overwrite the hash with the current one for the next call -if [[ $(< "${CHANGE_DETECTION_HASH_FILE_PATH}") == "$CURRENT_FILES_HASH" ]] ; then +if [[ $(< "${hashFilePath}") == "${CURRENT_FILES_HASH}" ]] ; then echo 0 # 0=No change detected else if ! ${readonlyMode}; then # Write the updated hash into the file containing the hash of the files list for the next call - echo "$CURRENT_FILES_HASH" > "${CHANGE_DETECTION_HASH_FILE_PATH}" + echo "${CURRENT_FILES_HASH}" > "${hashFilePath}" echo "detectChangedFiles: Change detection file updated" >&2 else echo "detectChangedFiles: Skipping file update with content (=hash) ${CURRENT_FILES_HASH}" >&2 From d1f035345f5ed41157dc974cd55b5007a9124163 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Sun, 18 Aug 2024 11:21:54 +0200 Subject: [PATCH 13/16] Scan Typescript sources only on changes. --- README.md | 1 + scripts/findPathsToScan.sh | 15 ---------- scripts/resetAndScanChanged.sh | 7 +++-- scripts/scanTypescript.sh | 53 ++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 18 deletions(-) create mode 100755 scripts/scanTypescript.sh diff --git a/README.md b/README.md index a057a56d9..2be3e9960 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ The [Code Structure Analysis Pipeline](./.github/workflows/java-code-analysis.ym - How can i trigger a full re-scan of all artifacts? 👉 Delete the file `artifactsChangeDetectionHash.txt` in the `artifacts` directory. + 👉 Delete the file `typescriptFileChangeDetectionHashFile.txt` in the `source` directory to additionally re-scan Typescript projects. - How can i enable PDF generation for Jupyter Notebooks (depends on chromium, takes more time)? 👉 Set environment variable `ENABLE_JUPYTER_NOTEBOOK_PDF_GENERATION` to anything except an empty string. Example: diff --git a/scripts/findPathsToScan.sh b/scripts/findPathsToScan.sh index 1830891a1..0af6f2515 100755 --- a/scripts/findPathsToScan.sh +++ b/scripts/findPathsToScan.sh @@ -39,21 +39,6 @@ else fi if [ -d "./${SOURCE_DIRECTORY}" ] ; then - if command -v "npx" &> /dev/null ; then - # TODO: Remove patchJQAssistantTypescriptPlugin when issue is resolved: https://github.com/jqassistant-plugin/jqassistant-typescript-plugin/issues/125 - source "${SCRIPTS_DIR}/patchJQAssistantTypescriptPlugin.sh" >&2 - echo "findPathsToScan: Scanning Typescript source using @jqassistant/ts-lce..." >&2 - # Note: The npx command will be executed in the source directory using a subshell by putting parentheses around it. - # The subshell is the reason why it isn't needed to change back to the main directory after execution. - # Note: This script must not output anything except for the return code to stdout, - # all output of the scanning needs to be redirected to stderr using ">&2". - # For later troubleshooting, the output is also copied to a dedicated log file using "tee". - # Note: Don't worry about the hardcoded version number. It will be updated by Renovate using a custom Manager. - ( cd "./${SOURCE_DIRECTORY}" && npx --yes @jqassistant/ts-lce@1.2.1 --extension React 2>&1 | tee "./../runtime/logs/jqassistant-typescript-scan.log" >&2 || exit ) - else - echo "findPathToScan Error: Command npx not found. It's needed to execute @jqassistant/ts-lce to scan Typescript projects." >&2 - fi - # Scan Typescript analysis json data files in the source directory typescriptAnalysisFiles="$(find "./${SOURCE_DIRECTORY}" \ -type d -name "node_modules" -prune -o \ diff --git a/scripts/resetAndScanChanged.sh b/scripts/resetAndScanChanged.sh index 43c409e7a..5abd5d758 100755 --- a/scripts/resetAndScanChanged.sh +++ b/scripts/resetAndScanChanged.sh @@ -4,7 +4,7 @@ # Note: "resetAndScan" expects jQAssistant to be installed in the "tools" directory. -# Requires resetAndScan.sh, copyPackageJsonFiles.sh, detectChangedFiles.sh, findPathsToScan.sh +# Requires resetAndScan.sh, copyPackageJsonFiles.sh, scanTypescript.sh, detectChangedFiles.sh, findPathsToScan.sh # Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands) set -o errexit -o pipefail @@ -16,12 +16,13 @@ set -o errexit -o pipefail SCRIPTS_DIR=${SCRIPTS_DIR:-$( CDPATH=. cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P )} # Repository directory containing the shell scripts echo "resetAndScanChanged SCRIPTS_DIR=${SCRIPTS_DIR}" -filesAndDirectoriesToScan=$( source "${SCRIPTS_DIR}/findPathsToScan.sh" ) - # Prepare scan # TODO "copyPackageJsonFiles.sh" can be deleted here when the following issue is resolved: # https://github.com/jqassistant-plugin/jqassistant-npm-plugin/issues/5 source "${SCRIPTS_DIR}/copyPackageJsonFiles.sh" +source "${SCRIPTS_DIR}/scanTypescript.sh" + +filesAndDirectoriesToScan=$( source "${SCRIPTS_DIR}/findPathsToScan.sh" ) # Scan and analyze Artifacts when they were changed changeDetectionReturnCode=$( source "${SCRIPTS_DIR}/detectChangedFiles.sh" --readonly --paths "${filesAndDirectoriesToScan}") diff --git a/scripts/scanTypescript.sh b/scripts/scanTypescript.sh new file mode 100755 index 000000000..431e278c4 --- /dev/null +++ b/scripts/scanTypescript.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# Executes the npm package @jqassistant/ts-lc using npx to scan the Typescript projects in the source directory and create an intermediate json data file for the jQAssistant Typescript plugin. + +# Uses: patchJQAssistantTypescriptPlugin.sh, detectChangedFiles.sh + +# Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands) +set -o errexit -o pipefail + +ARTIFACTS_DIRECTORY=${ARTIFACTS_DIRECTORY:-"artifacts"} +SOURCE_DIRECTORY=${SOURCE_DIRECTORY:-"source"} + +## Get this "scripts" directory if not already set +# Even if $BASH_SOURCE is made for Bourne-like shells it is also supported by others and therefore here the preferred solution. +# CDPATH reduces the scope of the cd command to potentially prevent unintended directory changes. +# This way non-standard tools like readlink aren't needed. +SCRIPTS_DIR=${SCRIPTS_DIR:-$( CDPATH=. cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P )} # Repository directory containing the shell scripts +echo "scanTypescript SCRIPTS_DIR=${SCRIPTS_DIR}" >&2 + +if [ ! -d "./${SOURCE_DIRECTORY}" ] ; then + echo "scanTypescript: Source directory '${SOURCE_DIRECTORY}' doesn't exist. The scan will therefore be skipped." >&2 + return 0 +fi + +if ! command -v "npx" &> /dev/null ; then + echo "scanTypescript Error: Command npx not found. It's needed to execute @jqassistant/ts-lce to scan Typescript projects." >&2 + exit 1 +fi + +# Scan and analyze Artifacts when they were changed +changeDetectionHashFilePath="./${SOURCE_DIRECTORY}/typescriptFileChangeDetectionHashFile.txt" +changeDetectionReturnCode=$( source "${SCRIPTS_DIR}/detectChangedFiles.sh" --readonly --hashfile "${changeDetectionHashFilePath}" --paths "./${SOURCE_DIRECTORY}") + +if [ "${changeDetectionReturnCode}" == "0" ] ; then + echo "scanTypescript: Files unchanged. Scan skipped." +else + echo "scanTypescript: Detected change (${changeDetectionReturnCode}). Scanning Typescript source using @jqassistant/ts-lce." + + # TODO: Remove patchJQAssistantTypescriptPlugin when issue is resolved: https://github.com/jqassistant-plugin/jqassistant-typescript-plugin/issues/125 + source "${SCRIPTS_DIR}/patchJQAssistantTypescriptPlugin.sh" >&2 + + # Note: The npx command will be executed in the source directory using a subshell by putting parentheses around it. + # The subshell is the reason why it isn't needed to change back to the main directory after execution. + # Note: This script must not output anything except for the return code to stdout, + # all output of the scanning needs to be redirected to stderr using ">&2". + # For later troubleshooting, the output is also copied to a dedicated log file using "tee". + # Note: Don't worry about the hardcoded version number. It will be updated by Renovate using a custom Manager. + ( cd "./${SOURCE_DIRECTORY}" && npx --yes @jqassistant/ts-lce@1.2.1 --extension React 2>&1 | tee "./../runtime/logs/jqassistant-typescript-scan.log" >&2 || exit ) + + changeDetectionReturnCode=$( source "${SCRIPTS_DIR}/detectChangedFiles.sh" --hashfile "${changeDetectionHashFilePath}" --paths "./${SOURCE_DIRECTORY}") +fi + + From efcf4b2614aea01275a09d71d1014d7b21beb145 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:33:26 +0200 Subject: [PATCH 14/16] Remove ts-lce patch from commit 291bcec. Issue jqassistant-plugin/jqassistant-typescript-plugin#125 fixed with @jqassistant/ts-lce@1.2.1. --- scripts/patchJQAssistantTypescriptPlugin.sh | 37 --------------------- scripts/scanTypescript.sh | 3 -- 2 files changed, 40 deletions(-) delete mode 100755 scripts/patchJQAssistantTypescriptPlugin.sh diff --git a/scripts/patchJQAssistantTypescriptPlugin.sh b/scripts/patchJQAssistantTypescriptPlugin.sh deleted file mode 100755 index eca960c8d..000000000 --- a/scripts/patchJQAssistantTypescriptPlugin.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash - -# Patches jQAssistant Typescript Plugin as a workaround for https://github.com/jqassistant-plugin/jqassistant-typescript-plugin/issues/125 - -# Note: As soon as the issue is resolved, this file and its call can be deleted. - -# Requires "apply" - -# Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands) -set -o errexit -o pipefail - -## Get this "scripts" directory if not already set -# Even if $BASH_SOURCE is made for Bourne-like shells it is also supported by others and therefore here the preferred solution. -# CDPATH reduces the scope of the cd command to potentially prevent unintended directory changes. -# This way non-standard tools like readlink aren't needed. -SCRIPTS_DIR=${SCRIPTS_DIR:-$( CDPATH=. cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P )} # Repository directory containing the shell scripts -echo "setupJQAssistant: SCRIPTS_DIR=$SCRIPTS_DIR" - -if ! command -v "npx" &> /dev/null ; then - echo "patchJQAssistantTypescriptPlugin Error: Command npx not found. It's needed to install @jqassistant/ts-lce temporarily." -fi - -echo "patchJQAssistantTypescriptPlugin: Installing jQAssistant Typescript Plugin @jqassistant/ts-lce using npx" -jqassistant_typescript_plugin_installation_path=$(npx --yes --package @jqassistant/ts-lce@1.2.1 which jqa-ts-lce) -echo "patchJQAssistantTypescriptPlugin: jqassistant_typescript_plugin_installation_path=${jqassistant_typescript_plugin_installation_path}" - -jqassistant_typescript_plugin_bin_path=$(dirname -- "${jqassistant_typescript_plugin_installation_path}") -jqassistant_typescript_plugin_utils_file_to_patch_relative="${jqassistant_typescript_plugin_bin_path}/../@jqassistant/ts-lce/dist/src/core/utils/project.utils.js" -echo "patchJQAssistantTypescriptPlugin: jqassistant_typescript_plugin_utils_file_to_patch_relative=${jqassistant_typescript_plugin_utils_file_to_patch_relative}" - -jqassistant_typescript_plugin_utils_path_resolved=$( CDPATH=. cd -- "$(dirname -- "${jqassistant_typescript_plugin_utils_file_to_patch_relative}")" && pwd -P ) -jqassistant_typescript_plugin_utils_file_to_patch="${jqassistant_typescript_plugin_utils_path_resolved}/project.utils.js" -echo "patchJQAssistantTypescriptPlugin: jqassistant_typescript_plugin_utils_file_to_patch=${jqassistant_typescript_plugin_utils_file_to_patch}" - -echo "patchJQAssistantTypescriptPlugin: Patching jQAssistant Typescript Plugin @jqassistant/ts-lce" -patch --forward -p1 "${jqassistant_typescript_plugin_utils_file_to_patch_relative}" -i "${SCRIPTS_DIR}/patches/@jqassistant+ts-lce+1.2.0.patch" || true -echo "patchJQAssistantTypescriptPlugin: Successfully patched jQAssistant Typescript Plugin @jqassistant/ts-lce" \ No newline at end of file diff --git a/scripts/scanTypescript.sh b/scripts/scanTypescript.sh index 431e278c4..5c2c485d1 100755 --- a/scripts/scanTypescript.sh +++ b/scripts/scanTypescript.sh @@ -36,9 +36,6 @@ if [ "${changeDetectionReturnCode}" == "0" ] ; then else echo "scanTypescript: Detected change (${changeDetectionReturnCode}). Scanning Typescript source using @jqassistant/ts-lce." - # TODO: Remove patchJQAssistantTypescriptPlugin when issue is resolved: https://github.com/jqassistant-plugin/jqassistant-typescript-plugin/issues/125 - source "${SCRIPTS_DIR}/patchJQAssistantTypescriptPlugin.sh" >&2 - # Note: The npx command will be executed in the source directory using a subshell by putting parentheses around it. # The subshell is the reason why it isn't needed to change back to the main directory after execution. # Note: This script must not output anything except for the return code to stdout, From ee279ca150bd0ab61b7920a6f4e32524b1b60562 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:57:07 +0200 Subject: [PATCH 15/16] Fix low coupling weight = 0 on when there are no declarations --- ...script_internal_module_dependencies.cypher | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/cypher/DependsOn_Relationship_Weights/Add_fine_grained_weights_for_Typescript_internal_module_dependencies.cypher b/cypher/DependsOn_Relationship_Weights/Add_fine_grained_weights_for_Typescript_internal_module_dependencies.cypher index eecea8264..7027141c5 100644 --- a/cypher/DependsOn_Relationship_Weights/Add_fine_grained_weights_for_Typescript_internal_module_dependencies.cypher +++ b/cypher/DependsOn_Relationship_Weights/Add_fine_grained_weights_for_Typescript_internal_module_dependencies.cypher @@ -2,14 +2,16 @@ // Get the top level dependency between a Typescript module and the external modules it uses MATCH (source:TS:Module)-[moduleDependency:DEPENDS_ON]->(target:Module) -// Exclude all targets where an ExternalModule was found that resolves to them -// because those are covered in the fine grained weights for "ExternalModule"s. - WHERE NOT EXISTS { (target)<-[:RESOLVES_TO]-(resolvedTarget:ExternalModule) } +// Exclude all that already have extended weight properties +// for example because those were covered in the fine grained weights for "ExternalModule"s. + WHERE moduleDependency.declarationCount IS NULL +// Ruling out resolved targets also filters out entries that aren't covered by the fine grained weights for "ExternalModule"s. +// Therefore, the exists filter is commented out for now and replaced by focussing on missing detailed weight properties to catch them all. +//WHERE NOT EXISTS { (target)<-[:RESOLVES_TO]-(resolvedTarget:ExternalModule) } WITH source ,target ,moduleDependency ,moduleDependency.cardinality AS targetModuleCardinality - // Get optional external (e.g. type) declarations that the external module (target) provides and the source module uses OPTIONAL MATCH (source)-[elementDependency:DEPENDS_ON|EXPORTS]->(elementType:TS)<-[:EXPORTS]-(target) WITH source @@ -40,13 +42,18 @@ OPTIONAL MATCH (source)-[abstractDependency:DEPENDS_ON|EXPORTS]->(abstractType:T // - "lowCouplingElement25PercentWeight" subtracts 75% of the weights for abstract types like Interfaces and Type aliases // to compensate for their low coupling influence. Not included "high coupling" elements like Functions and Classes // remain in the weight as they were. The same applies for "lowCouplingElement10PercentWeight" but with in a stronger manner. +// If there are no declarations and therefore the elementTypeCardinality is zero then the original targetModuleCardinality is used. SET moduleDependency.declarationCount = elementTypeCount ,moduleDependency.abstractTypeCount = abstractTypeCount ,moduleDependency.abstractTypeCardinality = abstractTypeCardinality - ,moduleDependency.lowCouplingElement25PercentWeight = - toInteger(elementTypeCardinality - round(abstractTypeCardinality * 0.75)) - ,moduleDependency.lowCouplingElement10PercentWeight = - toInteger(elementTypeCardinality - round(abstractTypeCardinality * 0.90)) + ,moduleDependency.lowCouplingElement25PercentWeight = toInteger( + coalesce(nullif(elementTypeCardinality, 0), targetModuleCardinality) - + round(abstractTypeCardinality * 0.75) + ) + ,moduleDependency.lowCouplingElement10PercentWeight = toInteger( + coalesce(nullif(elementTypeCardinality, 0), targetModuleCardinality) - + round(abstractTypeCardinality * 0.90) + ) RETURN source.globalFqn AS sourceName ,target.globalFqn AS targetName ,elementTypeCount From 29addeb673cb7ce411dbe0f76d85bbd54d112f83 Mon Sep 17 00:00:00 2001 From: JohT <7671054+JohT@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:34:56 +0200 Subject: [PATCH 16/16] Fix missing runtime logs directory on Windows --- scripts/scanTypescript.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/scanTypescript.sh b/scripts/scanTypescript.sh index 5c2c485d1..fd87171cf 100755 --- a/scripts/scanTypescript.sh +++ b/scripts/scanTypescript.sh @@ -42,6 +42,7 @@ else # all output of the scanning needs to be redirected to stderr using ">&2". # For later troubleshooting, the output is also copied to a dedicated log file using "tee". # Note: Don't worry about the hardcoded version number. It will be updated by Renovate using a custom Manager. + mkdir -p "./runtime/logs" ( cd "./${SOURCE_DIRECTORY}" && npx --yes @jqassistant/ts-lce@1.2.1 --extension React 2>&1 | tee "./../runtime/logs/jqassistant-typescript-scan.log" >&2 || exit ) changeDetectionReturnCode=$( source "${SCRIPTS_DIR}/detectChangedFiles.sh" --hashfile "${changeDetectionHashFilePath}" --paths "./${SOURCE_DIRECTORY}")