diff --git a/.github/workflows/internal-validate-cli-outputs.yml b/.github/workflows/internal-validate-cli-outputs.yml new file mode 100644 index 0000000..65875eb --- /dev/null +++ b/.github/workflows/internal-validate-cli-outputs.yml @@ -0,0 +1,72 @@ +name: ⚙️ Internal - Validate CLI Outputs + +on: + push: + branches: + - 'main' + pull_request: + branches: + - 'main' + workflow_dispatch: + +env: + # Global configuration + DOTNET_VERSION: '6.0.x' + BUILD_CONFIGURATION: Debug + +jobs: + validate-cli-outputs: + name: Validate CodeQlToolkit `query generate new-query` subcommand + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Restore dependencies + run: dotnet restore + + - name: Build project + run: dotnet build -c ${{ env.BUILD_CONFIGURATION }} --no-restore + + - name: Set test directory + run: echo "TEST_DIR=${{ runner.temp }}/qlt-cli-e2e-test" >> $GITHUB_ENV + + - name: Prepare test environment + run: | + # Create test directory + mkdir -p "$TEST_DIR" + + # Copy configuration file + cp example/qlt.conf.json "$TEST_DIR" + + ## Using the 'latest' version of the `codeql` CLI may eventually + ## cause issues, where the `qlt.conf.json` file specifies an exact + ## version of the CLI. For now, we live with the risk, and the issues + ## surfaced might actually be useful to identify emerging problems + ## in the `codeql` CLI -- which is only required here for extra + ## debugging of any queries with test failures. + - name: Install `codeql` CLI + id: install-codeql + uses: ./.github/actions/install-codeql + with: + add-to-path: 'true' + codeql-cli-version: 'latest' + codeql-stdlib-version: 'latest' + + - name: Run end-to-end CLI validation + run: ./scripts/validate-cli-e2e.sh + + - name: Upload test artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: cli-validation-artifacts + path: ${{ runner.temp }}/qlt-cli-e2e-test/** + if-no-files-found: warn + retention-days: 3 diff --git a/CodeQLToolkit.sln b/CodeQLToolkit.sln index 54867ee..7fa8061 100644 --- a/CodeQLToolkit.sln +++ b/CodeQLToolkit.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeQLToolkit.Features", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeQLToolkit.Shared.Tests", "test\CodeQLToolkit.Shared.Tests\CodeQLToolkit.Shared.Tests.csproj", "{E57121C2-2A2E-487C-9C32-A7D8914E9123}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeQLToolkit.Features.Tests", "test\CodeQLToolkit.Features.Tests\CodeQLToolkit.Features.Tests.csproj", "{F1234567-89AB-CDEF-0123-456789ABCDEF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +57,10 @@ Global {E57121C2-2A2E-487C-9C32-A7D8914E9123}.Debug|Any CPU.Build.0 = Debug|Any CPU {E57121C2-2A2E-487C-9C32-A7D8914E9123}.Release|Any CPU.ActiveCfg = Release|Any CPU {E57121C2-2A2E-487C-9C32-A7D8914E9123}.Release|Any CPU.Build.0 = Release|Any CPU + {F1234567-89AB-CDEF-0123-456789ABCDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1234567-89AB-CDEF-0123-456789ABCDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1234567-89AB-CDEF-0123-456789ABCDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1234567-89AB-CDEF-0123-456789ABCDEF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -65,6 +71,7 @@ Global {A1F78CBC-9AE9-4360-A06B-395F368B183A} = {05645E0E-835A-4627-BDAE-C27EC39B23EE} {B85F1529-3321-4AD2-924A-6EA478145DC5} = {667B480C-D805-4A9C-AC1F-D9FCCF3DB57C} {E57121C2-2A2E-487C-9C32-A7D8914E9123} = {05645E0E-835A-4627-BDAE-C27EC39B23EE} + {F1234567-89AB-CDEF-0123-456789ABCDEF} = {05645E0E-835A-4627-BDAE-C27EC39B23EE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BF8F3B81-F4DC-4608-A41C-F00F3E5A05CA} diff --git a/example/cpp/customizations/src/codeql-pack.lock.yml b/example/cpp/customizations/src/codeql-pack.lock.yml index 4edf97c..95bae44 100644 --- a/example/cpp/customizations/src/codeql-pack.lock.yml +++ b/example/cpp/customizations/src/codeql-pack.lock.yml @@ -2,17 +2,25 @@ lockVersion: 1.0.0 dependencies: codeql/cpp-all: - version: 0.12.2 + version: 5.4.1 codeql/dataflow: - version: 0.1.5 + version: 2.0.13 + codeql/mad: + version: 1.0.29 + codeql/quantum: + version: 0.0.7 codeql/rangeanalysis: - version: 0.0.4 + version: 1.0.29 codeql/ssa: - version: 0.2.5 + version: 2.0.5 codeql/tutorial: - version: 0.2.5 + version: 1.0.29 + codeql/typeflow: + version: 1.0.29 codeql/typetracking: - version: 0.2.5 + version: 2.0.13 codeql/util: - version: 0.2.5 + version: 2.0.16 + codeql/xml: + version: 1.0.29 compiled: false diff --git a/example/cpp/customizations/src/qlpack.yml b/example/cpp/customizations/src/qlpack.yml index 06c5c2a..fe91aab 100644 --- a/example/cpp/customizations/src/qlpack.yml +++ b/example/cpp/customizations/src/qlpack.yml @@ -1,5 +1,5 @@ library: true name: qlt/cpp-customizations -version: 0.0.1 +version: 0.0.2 dependencies: - "codeql/cpp-all": "0.12.2" \ No newline at end of file + "codeql/cpp-all": "5.4.1" \ No newline at end of file diff --git a/example/cpp/customizations/test/codeql-pack.lock.yml b/example/cpp/customizations/test/codeql-pack.lock.yml index 4edf97c..95bae44 100644 --- a/example/cpp/customizations/test/codeql-pack.lock.yml +++ b/example/cpp/customizations/test/codeql-pack.lock.yml @@ -2,17 +2,25 @@ lockVersion: 1.0.0 dependencies: codeql/cpp-all: - version: 0.12.2 + version: 5.4.1 codeql/dataflow: - version: 0.1.5 + version: 2.0.13 + codeql/mad: + version: 1.0.29 + codeql/quantum: + version: 0.0.7 codeql/rangeanalysis: - version: 0.0.4 + version: 1.0.29 codeql/ssa: - version: 0.2.5 + version: 2.0.5 codeql/tutorial: - version: 0.2.5 + version: 1.0.29 + codeql/typeflow: + version: 1.0.29 codeql/typetracking: - version: 0.2.5 + version: 2.0.13 codeql/util: - version: 0.2.5 + version: 2.0.16 + codeql/xml: + version: 1.0.29 compiled: false diff --git a/example/cpp/customizations/test/qlpack.yml b/example/cpp/customizations/test/qlpack.yml index f4e5b27..04d51d4 100644 --- a/example/cpp/customizations/test/qlpack.yml +++ b/example/cpp/customizations/test/qlpack.yml @@ -1,6 +1,6 @@ library: true name: qlt/cpp-customizations-tests -version: 0.0.1 +version: 0.0.2 dependencies: "qlt/cpp-customizations": "*" extractor: cpp \ No newline at end of file diff --git a/example/cpp/stuff/src/codeql-pack.lock.yml b/example/cpp/stuff/src/codeql-pack.lock.yml index 4edf97c..95bae44 100644 --- a/example/cpp/stuff/src/codeql-pack.lock.yml +++ b/example/cpp/stuff/src/codeql-pack.lock.yml @@ -2,17 +2,25 @@ lockVersion: 1.0.0 dependencies: codeql/cpp-all: - version: 0.12.2 + version: 5.4.1 codeql/dataflow: - version: 0.1.5 + version: 2.0.13 + codeql/mad: + version: 1.0.29 + codeql/quantum: + version: 0.0.7 codeql/rangeanalysis: - version: 0.0.4 + version: 1.0.29 codeql/ssa: - version: 0.2.5 + version: 2.0.5 codeql/tutorial: - version: 0.2.5 + version: 1.0.29 + codeql/typeflow: + version: 1.0.29 codeql/typetracking: - version: 0.2.5 + version: 2.0.13 codeql/util: - version: 0.2.5 + version: 2.0.16 + codeql/xml: + version: 1.0.29 compiled: false diff --git a/example/cpp/stuff/src/qlpack.yml b/example/cpp/stuff/src/qlpack.yml index 51c46db..8391757 100644 --- a/example/cpp/stuff/src/qlpack.yml +++ b/example/cpp/stuff/src/qlpack.yml @@ -1,9 +1,9 @@ --- library: true name: qlt55/stuff -version: 0.0.1 +version: 0.0.2 description: Default description suites: license: dependencies: - codeql/cpp-all: "0.12.2" \ No newline at end of file + codeql/cpp-all: "5.4.1" \ No newline at end of file diff --git a/example/cpp/stuff/test/codeql-pack.lock.yml b/example/cpp/stuff/test/codeql-pack.lock.yml index 4edf97c..95bae44 100644 --- a/example/cpp/stuff/test/codeql-pack.lock.yml +++ b/example/cpp/stuff/test/codeql-pack.lock.yml @@ -2,17 +2,25 @@ lockVersion: 1.0.0 dependencies: codeql/cpp-all: - version: 0.12.2 + version: 5.4.1 codeql/dataflow: - version: 0.1.5 + version: 2.0.13 + codeql/mad: + version: 1.0.29 + codeql/quantum: + version: 0.0.7 codeql/rangeanalysis: - version: 0.0.4 + version: 1.0.29 codeql/ssa: - version: 0.2.5 + version: 2.0.5 codeql/tutorial: - version: 0.2.5 + version: 1.0.29 + codeql/typeflow: + version: 1.0.29 codeql/typetracking: - version: 0.2.5 + version: 2.0.13 codeql/util: - version: 0.2.5 + version: 2.0.16 + codeql/xml: + version: 1.0.29 compiled: false diff --git a/example/cpp/stuff/test/qlpack.yml b/example/cpp/stuff/test/qlpack.yml index 3bdbc60..f7bcae3 100644 --- a/example/cpp/stuff/test/qlpack.yml +++ b/example/cpp/stuff/test/qlpack.yml @@ -1,5 +1,5 @@ name: qlt55/stuff-tests -version: 0.0.0 +version: 0.0.2 description: Default description suites: license: diff --git a/example/cpp/stuff2/src/codeql-pack.lock.yml b/example/cpp/stuff2/src/codeql-pack.lock.yml index 4edf97c..95bae44 100644 --- a/example/cpp/stuff2/src/codeql-pack.lock.yml +++ b/example/cpp/stuff2/src/codeql-pack.lock.yml @@ -2,17 +2,25 @@ lockVersion: 1.0.0 dependencies: codeql/cpp-all: - version: 0.12.2 + version: 5.4.1 codeql/dataflow: - version: 0.1.5 + version: 2.0.13 + codeql/mad: + version: 1.0.29 + codeql/quantum: + version: 0.0.7 codeql/rangeanalysis: - version: 0.0.4 + version: 1.0.29 codeql/ssa: - version: 0.2.5 + version: 2.0.5 codeql/tutorial: - version: 0.2.5 + version: 1.0.29 + codeql/typeflow: + version: 1.0.29 codeql/typetracking: - version: 0.2.5 + version: 2.0.13 codeql/util: - version: 0.2.5 + version: 2.0.16 + codeql/xml: + version: 1.0.29 compiled: false diff --git a/example/cpp/stuff2/src/qlpack.yml b/example/cpp/stuff2/src/qlpack.yml index 2dad82b..b796650 100644 --- a/example/cpp/stuff2/src/qlpack.yml +++ b/example/cpp/stuff2/src/qlpack.yml @@ -1,9 +1,9 @@ --- library: true name: qlt2/stuff2 -version: 0.0.1 +version: 0.0.2 description: Default description suites: license: dependencies: - codeql/cpp-all: "0.12.2" \ No newline at end of file + codeql/cpp-all: "5.4.1" \ No newline at end of file diff --git a/example/cpp/stuff2/test/codeql-pack.lock.yml b/example/cpp/stuff2/test/codeql-pack.lock.yml index 4edf97c..95bae44 100644 --- a/example/cpp/stuff2/test/codeql-pack.lock.yml +++ b/example/cpp/stuff2/test/codeql-pack.lock.yml @@ -2,17 +2,25 @@ lockVersion: 1.0.0 dependencies: codeql/cpp-all: - version: 0.12.2 + version: 5.4.1 codeql/dataflow: - version: 0.1.5 + version: 2.0.13 + codeql/mad: + version: 1.0.29 + codeql/quantum: + version: 0.0.7 codeql/rangeanalysis: - version: 0.0.4 + version: 1.0.29 codeql/ssa: - version: 0.2.5 + version: 2.0.5 codeql/tutorial: - version: 0.2.5 + version: 1.0.29 + codeql/typeflow: + version: 1.0.29 codeql/typetracking: - version: 0.2.5 + version: 2.0.13 codeql/util: - version: 0.2.5 + version: 2.0.16 + codeql/xml: + version: 1.0.29 compiled: false diff --git a/example/cpp/stuff2/test/qlpack.yml b/example/cpp/stuff2/test/qlpack.yml index 2e1f1f9..777e5b8 100644 --- a/example/cpp/stuff2/test/qlpack.yml +++ b/example/cpp/stuff2/test/qlpack.yml @@ -1,5 +1,5 @@ name: qlt2/stuff2-tests -version: 0.0.0 +version: 0.0.2 description: Default description suites: license: diff --git a/example/qlt.conf.json b/example/qlt.conf.json index 9beeefd..7189aa6 100644 --- a/example/qlt.conf.json +++ b/example/qlt.conf.json @@ -1,9 +1,9 @@ { - "CodeQLCLI": "2.15.5", - "CodeQLStandardLibrary": "codeql-cli/v2.15.5", - "CodeQLCLIBundle": "codeql-bundle-v2.15.5", + "CodeQLCLI": "2.22.4", + "CodeQLStandardLibrary": "codeql-cli/v2.22.4", + "CodeQLCLIBundle": "codeql-bundle-v2.22.4", "EnableCustomCodeQLBundles": true, - "CodeQLStandardLibraryIdent": "codeql-cli_v2.15.5", + "CodeQLStandardLibraryIdent": "codeql-cli_v2.22.4", "CodeQLPackConfiguration" : [ { "Name": "qlt/cpp-customizations", diff --git a/scripts/run-unit-tests.sh b/scripts/run-unit-tests.sh new file mode 100755 index 0000000..381dca3 --- /dev/null +++ b/scripts/run-unit-tests.sh @@ -0,0 +1,318 @@ +#!/bin/bash + +# Script: run-unit-tests.sh +# Purpose: Run CodeQL unit tests for generated queries +# Usage: ./run-unit-tests.sh [OPTIONS] + +set -euo pipefail + +# Color codes for output +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Logging functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" >&2 +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" >&2 +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" >&2 +} + +# Configuration - Centralized path variables +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +QLT_PROJECT_DIR="$REPO_ROOT/src/CodeQLToolkit.Core" +QLT_PROJECT_FILE="$QLT_PROJECT_DIR/CodeQLToolkit.Core.csproj" + +# Default configuration +TEST_DIR="" +LANGUAGES="" +VERBOSE=false +HELP=false + +# Usage information +show_help() { + cat << EOF +Usage: $0 [OPTIONS] + +Run CodeQL unit tests for generated queries in a test directory. + +OPTIONS: + -d, --test-dir DIR Test directory containing language subdirectories + -l, --languages LANGS Comma-separated list of languages to test (e.g., "python,java") + -v, --verbose Enable verbose output + -h, --help Show this help message + +EXAMPLES: + $0 --test-dir /tmp/qlt-test --languages python,java + $0 -d /tmp/qlt-test -l python -v + +DESCRIPTION: + This script runs CodeQL unit tests for queries generated by the CodeQL Toolkit. + It expects a directory structure like: + + TEST_DIR/ + ├── python/ + │ └── testpack-python/ + │ └── test/ + └── java/ + └── testpack-java/ + └── test/ + + The script will: + 1. Execute unit tests for each language + 2. Validate test results + 3. Generate a summary report + +EOF +} + +# Parse command line arguments +parse_args() { + while [[ $# -gt 0 ]]; do + case $1 in + -d|--test-dir) + TEST_DIR="$2" + shift 2 + ;; + -l|--languages) + LANGUAGES="$2" + shift 2 + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -h|--help) + HELP=true + shift + ;; + *) + log_error "Unknown option: $1" + show_help + exit 1 + ;; + esac + done +} + +# Validate required parameters +validate_params() { + if [[ "$HELP" == "true" ]]; then + show_help + exit 0 + fi + + if [[ -z "$TEST_DIR" ]]; then + log_error "Test directory is required. Use --test-dir option." + show_help + exit 1 + fi + + if [[ ! -d "$TEST_DIR" ]]; then + log_error "Test directory does not exist: $TEST_DIR" + exit 1 + fi + + if [[ -z "$LANGUAGES" ]]; then + log_error "Languages are required. Use --languages option." + show_help + exit 1 + fi +} + +# Execute unit tests for a language +run_unit_tests() { + local language="$1" + local work_dir="$2" + + log_info "Running unit tests for $language..." + + # Get environment info + local os_name=$(uname -s) + if [[ "$os_name" == "Darwin" ]]; then + os_name="macOS" + fi + + log_info " → Environment: $os_name, CodeQL CLI: Startup... +unknown, Stdlib: codeql-cli_vStartup... +unknown" + + # Find the QLT project using centralized variables + if [[ ! -f "$QLT_PROJECT_FILE" ]]; then + log_error "Could not find QLT project at: $QLT_PROJECT_FILE" + return 1 + fi + + # Change to the work directory since that's where QLT expects to be run from + local old_pwd=$(pwd) + cd "$work_dir" || return 1 + + # Create test results directory for the language + local test_results_dir="${work_dir}/${language}/test-results" + mkdir -p "$test_results_dir" + + # Execute tests using dotnet run + local test_output + if ! test_output=$(dotnet run --project "$QLT_PROJECT_FILE" -- test run execute-unit-tests \ + --language "$language" \ + --num-threads 2 \ + --work-dir "$test_results_dir" \ + --runner-os "$os_name" \ + --automation-type actions 2>&1); then + log_error "Unit test execution failed for $language" + if [[ "$VERBOSE" == "true" ]]; then + log_error "$test_output" + fi + cd "$old_pwd" + return 1 + fi + + cd "$old_pwd" + + if [[ "$VERBOSE" == "true" ]]; then + log_info "Test execution output for $language:" + echo "$test_output" + fi + + log_success "Unit tests completed for $language" + return 0 +} + +# Validate unit test results +validate_test_results() { + local language="$1" + local work_dir="$2" + + log_info "Validating unit test results for $language..." + + local results_dir="${work_dir}/${language}/test-results" + if [[ ! -d "$results_dir" ]]; then + log_error "Test results directory not found: $results_dir" + return 1 + fi + + # Find the QLT project using centralized variables + if [[ ! -f "$QLT_PROJECT_FILE" ]]; then + log_error "Could not find QLT project at: $QLT_PROJECT_FILE" + return 1 + fi + + # Change to the work directory since that's where QLT expects to be run from + local old_pwd=$(pwd) + cd "$work_dir" || return 1 + + local validation_output + if ! validation_output=$(dotnet run --project "$QLT_PROJECT_FILE" -- test run validate-unit-tests --pretty-print --results-directory "$results_dir" 2>&1); then + log_error "Test result validation failed" + if [[ "$VERBOSE" == "true" ]]; then + log_error "$validation_output" + fi + cd "$old_pwd" + return 1 + fi + + cd "$old_pwd" + + if [[ "$VERBOSE" == "true" ]]; then + log_info "Validation output:" + echo "$validation_output" + fi + + log_success "Test result validation passed" + return 0 +} + +# Main execution function +main() { + parse_args "$@" + validate_params + + log_info "Starting unit test execution..." + log_info "Test directory: $TEST_DIR" + log_info "Languages: $LANGUAGES" + + # Convert comma-separated languages to array + IFS=',' read -ra LANG_ARRAY <<< "$LANGUAGES" + + local passed=0 + local failed=0 + local failed_languages=() + + # Process each language + for language in "${LANG_ARRAY[@]}"; do + language=$(echo "$language" | xargs) # trim whitespace + + log_info "Processing language: $language" + + # Check if language directory exists + local lang_dir="$TEST_DIR/$language" + if [[ ! -d "$lang_dir" ]]; then + log_warning "Language directory not found: $lang_dir, skipping..." + continue + fi + + # Run tests for this language + local language_failed=false + if ! run_unit_tests "$language" "$TEST_DIR"; then + log_error "Unit test execution failed for $language" + language_failed=true + fi + + # Update counters + if [[ "$language_failed" == "true" ]]; then + ((failed++)) + failed_languages+=("$language") + log_error "Language $language validation failed" + else + ((passed++)) + log_success "Language $language validation complete" + fi + + # Validate all test results if any tests were run + if [[ $((passed + failed)) -gt 0 ]]; then + if ! validate_test_results "$language" "$TEST_DIR"; then + log_error "Unit test validation failed" + ((failed++)) + else + log_success "Unit test validation passed" + fi + fi + done + + # Print summary + echo + log_info "=== Unit Test Summary ===" + log_success "Unit Tests Passed: $passed" + if [[ $failed -gt 0 ]]; then + log_error "Unit Tests Failed: $failed" + if [[ ${#failed_languages[@]} -gt 0 ]]; then + log_error "Unit Tests Failed for Languages: ${failed_languages[*]}" + fi + else + log_success "No Unit Tests Failed" + fi + + if [[ $failed -eq 0 ]]; then + log_success "All unit tests passed! 🎉" + exit 0 + else + log_error "Some unit tests failed. 😞" + exit 1 + fi +} + +# Run main function with all arguments +main "$@" diff --git a/scripts/validate-cli-e2e.sh b/scripts/validate-cli-e2e.sh new file mode 100755 index 0000000..08b42bc --- /dev/null +++ b/scripts/validate-cli-e2e.sh @@ -0,0 +1,534 @@ +#!/bin/bash + +# End-to-end validation script for CodeQL Toolkit CLI +# This script validates the new-query subcommand functionality across all supported languages + +set -euo pipefail + +# Configuration - Centralized path variables +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +QLT_PROJECT_DIR="$REPO_ROOT/src/CodeQLToolkit.Core" +QLT_PROJECT_FILE="$QLT_PROJECT_DIR/CodeQLToolkit.Core.csproj" +QLT_BINARY="$QLT_PROJECT_DIR/bin/Debug/net6.0/CodeQLToolkit.Core.dll" +TEST_DIR="${TEST_DIR:-/tmp/qlt-cli-e2e-test}" +DEFAULT_LANGUAGES=("cpp" "csharp" "go" "java" "javascript" "python" "ruby") +DEFAULT_QUERY_KINDS=("problem" "path-problem") +LANGUAGES=("${DEFAULT_LANGUAGES[@]}") +QUERY_KINDS=("${DEFAULT_QUERY_KINDS[@]}") +RUN_TESTS=${RUN_TESTS:-true} # Default to running tests + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Logging functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Find CodeQL CLI executable path +find_codeql_cli() { + local cli_path="" + + # Try common installation paths + local common_paths=( + "$HOME/.codeql/codeql-cli_v2.22.4/codeql/codeql" + "$HOME/.codeql/codeql-bundle-v2.22.4/codeql/codeql" + "$HOME/.codeql/distributions/codeql-cli_v2.22.4/codeql/codeql" + "$HOME/.codeql/distributions/codeql-bundle-v2.22.4/codeql/codeql" + "$(which codeql 2>/dev/null)" + ) + + for path in "${common_paths[@]}"; do + if [[ -f "$path" && -x "$path" ]]; then + cli_path="$path" + break + fi + done + + # If not found in common paths, try to find it dynamically + if [[ -z "$cli_path" ]]; then + cli_path=$(find "$HOME/.codeql" -name "codeql" -type f -executable 2>/dev/null | head -1) + fi + + echo "$cli_path" +} + +# Get detailed compilation errors for a specific query file +get_detailed_compilation_errors() { + local query_file="$1" + local codeql_cli="$2" + + if [[ -z "$codeql_cli" || ! -f "$codeql_cli" ]]; then + log_warning "CodeQL CLI not found, cannot get detailed compilation errors" + return 1 + fi + + if [[ ! -f "$query_file" ]]; then + log_error "Query file not found: $query_file" + return 1 + fi + + log_info "Getting detailed compilation errors for: $(basename "$query_file")" + + # Run codeql query compile with verbose output + local compile_output + if compile_output=$("$codeql_cli" query compile --check-only -- "$query_file" 2>&1); then + log_info "Query compiled successfully (unexpected since validation failed)" + echo "$compile_output" + else + log_error "Detailed compilation errors:" + echo "$compile_output" + fi +} + +# Cleanup function +cleanup_at_start() { + if [[ -d "$TEST_DIR" ]]; then + log_info "Cleaning up previous test directory: $TEST_DIR" + rm -rf "$TEST_DIR" + fi +} + +# Parse command line arguments +parse_arguments() { + while [[ $# -gt 0 ]]; do + case $1 in + --languages) + if [[ -n "$2" && "$2" != --* ]]; then + IFS=',' read -ra LANGUAGES <<< "$2" + shift 2 + else + log_error "Error: --languages requires a comma-separated list of languages" + exit 1 + fi + ;; + --query-kinds) + if [[ -n "$2" && "$2" != --* ]]; then + IFS=',' read -ra QUERY_KINDS <<< "$2" + shift 2 + else + log_error "Error: --query-kinds requires a comma-separated list of query kinds" + exit 1 + fi + ;; + --run-tests) + if [[ -n "$2" && "$2" != --* ]]; then + RUN_TESTS="$2" + shift 2 + else + log_error "Error: --run-tests requires a boolean value (true/false)" + exit 1 + fi + ;; + --help|-h) + echo "Usage: $0 [--languages LANG1,LANG2,...] [--query-kinds KIND1,KIND2,...] [--run-tests true/false]" + echo "" + echo "Options:" + echo " --languages LANG1,LANG2,... Comma-separated list of languages to validate" + echo " Default: python,java,javascript,go,cpp,csharp,ruby" + echo " --query-kinds KIND1,KIND2,... Comma-separated list of query kinds to validate" + echo " Default: problem,path-problem" + echo " --run-tests true/false Whether to run unit tests (default: true)" + echo " --help, -h Show this help message" + echo "" + echo "Environment variables:" + echo " TEST_DIR Directory for test files (default: /tmp/qlt-cli-e2e-test)" + echo " RUN_TESTS Whether to run unit tests (default: true)" + echo "" + echo "Supported languages: ${DEFAULT_LANGUAGES[*]}" + echo "Supported query kinds: ${DEFAULT_QUERY_KINDS[*]}" + exit 0 + ;; + *) + log_error "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac + done + + # Validate that all specified languages are supported + for lang in "${LANGUAGES[@]}"; do + if [[ ! " ${DEFAULT_LANGUAGES[*]} " =~ " ${lang} " ]]; then + log_error "Unsupported language: $lang" + log_error "Supported languages: ${DEFAULT_LANGUAGES[*]}" + exit 1 + fi + done + + # Validate that all specified query kinds are supported + for kind in "${QUERY_KINDS[@]}"; do + if [[ ! " ${DEFAULT_QUERY_KINDS[*]} " =~ " ${kind} " ]]; then + log_error "Unsupported query kind: $kind" + log_error "Supported query kinds: ${DEFAULT_QUERY_KINDS[*]}" + exit 1 + fi + done + + # Validate run-tests parameter + if [[ "$RUN_TESTS" != "true" && "$RUN_TESTS" != "false" ]]; then + log_error "Invalid value for RUN_TESTS: $RUN_TESTS" + log_error "Supported values: true, false" + exit 1 + fi +} + +# Setup trap for cleanup +# Note: We don't clean up at exit to allow troubleshooting of generated files + +# Setup test environment +setup_test_environment() { + log_info "Setting up test environment..." + + # Clean and create test directory + cleanup_at_start + mkdir -p "$TEST_DIR" + cd "$TEST_DIR" + + # Initialize CodeQL workspace + log_info "Initializing CodeQL workspace..." + dotnet "$QLT_BINARY" query init + + # Create qlt.conf.json with latest CodeQL version + log_info "Creating qlt.conf.json configuration..." + cat > qlt.conf.json << 'EOF' +{ + "CodeQLCLI": "2.22.4", + "CodeQLStandardLibrary": "codeql-cli/v2.22.4", + "CodeQLCLIBundle": "codeql-bundle-v2.22.4", + "EnableCustomCodeQLBundles": false, + "CodeQLStandardLibraryIdent": "codeql-cli_v2.22.4" +} +EOF + + # Install CodeQL if not already installed + log_info "Installing CodeQL..." + dotnet "$QLT_BINARY" codeql run install || true + + log_success "Test environment setup complete" +} + +# Generate queries for all languages and query kinds +generate_all_queries() { + log_info "Generating queries for all languages and query kinds..." + + for language in "${LANGUAGES[@]}"; do + for kind in "${QUERY_KINDS[@]}"; do + local pack_name="testpack-$language" + # Generate query name with kind suffix to match expected validation + case "$kind" in + "problem") + local query_name="TestQueryKindProblem" + ;; + "path-problem") + local query_name="TestQueryKindPathProblem" + ;; + *) + local query_name="TestQueryKind${kind}" + ;; + esac + + log_info " → Generating $kind query for $language..." + if ! dotnet "$QLT_BINARY" query generate new-query \ + --language "$language" \ + --query-name "$query_name" \ + --pack "$pack_name" \ + --query-kind "$kind"; then + log_error "Failed to generate $kind query for $language" + return 1 + fi + done + done + + log_success "All queries generated successfully" +} + +# Install all QL packs (runs once for entire workspace) +install_all_packs() { + log_info "Installing QL packs for all languages..." + if ! dotnet "$QLT_BINARY" query run install-packs; then + log_error "Failed to install packs" + return 1 + fi + log_success "All packs installed successfully" +} + +# Run unit tests using the dedicated script +run_unit_tests_for_languages() { + local languages="$1" + local work_dir="$2" + + log_info "Running unit tests for languages: $languages" + + # Get the directory where this script is located + local script_dir="$SCRIPT_DIR" + local unit_test_script="$script_dir/run-unit-tests.sh" + + if [[ ! -f "$unit_test_script" ]]; then + log_error "Unit test script not found: $unit_test_script" + return 1 + fi + + if [[ ! -x "$unit_test_script" ]]; then + log_error "Unit test script is not executable: $unit_test_script" + return 1 + fi + + # Run the unit tests script + if "$unit_test_script" --test-dir "$work_dir" --languages "$languages"; then + log_success "Unit tests passed : --languages $languages" + return 0 + else + log_error "Unit tests failed : --languages $languages" + return 1 + fi +} + +# Validate single language +validate_language() { + local language="$1" + local pack_name="testpack-$language" + + log_info "Validating language: $language" + + # Validate queries (includes formatting, compilation, etc.) + log_info " → Validating queries for $language..." + + # Capture validation output to distinguish warnings from errors + local validation_output + validation_output=$(dotnet "$QLT_BINARY" validation run check-queries --language "$language" 2>&1) + local validation_exit_code=$? + + # Display the output for transparency + echo "$validation_output" + + # Check if there are actual errors (not just deprecation warnings) + if [[ $validation_exit_code -ne 0 ]]; then + # Check if the failures are only deprecation warnings about assume_small_delta + if echo "$validation_output" | grep -v "pragma 'assume_small_delta' is deprecated" | grep -q -E "(ERROR|FAILURE|Failed|Error:|Compilation failed|Fatal error)"; then + log_error "Query validation failed for $language with actual errors" + + # Try to get detailed compilation errors for failed queries + log_info "Attempting to get detailed compilation errors..." + local codeql_cli + codeql_cli=$(find_codeql_cli) + + if [[ -n "$codeql_cli" ]]; then + # Find all query files for this language and check them individually + local lang_dir="$language/testpack-$language" + local src_dir="$lang_dir/src" + + if [[ -d "$src_dir" ]]; then + # Use find to get query files (compatible with older bash) + while IFS= read -r -d '' query_file; do + log_info "Checking individual query: $(basename "$query_file")" + get_detailed_compilation_errors "$query_file" "$codeql_cli" + echo "---" + done < <(find "$src_dir" -name "*.ql" -type f -print0) + else + log_warning "Source directory not found: $src_dir" + fi + else + log_warning "CodeQL CLI not found, cannot provide detailed compilation errors" + fi + + return 1 + else + log_info "Query validation completed for $language (only deprecation warnings in standard library)" + fi + else + log_info "Query validation passed for $language" + fi + + # Step 4: Verify generated files exist and have correct content for each query kind + log_info " → Verifying generated files for $language..." + + local lang_dir="$language/$pack_name" + local src_dir="$lang_dir/src" + local test_dir="$lang_dir/test" + + # Check query files for each kind + for kind in "${QUERY_KINDS[@]}"; do + local query_name="TestQuery" + local clean_name + + # Generate expected clean name based on kind + case "$kind" in + "problem") + clean_name="TestQueryKindProblem" + ;; + "path-problem") + clean_name="TestQueryKindPathProblem" + ;; + *) + clean_name="TestQueryKind${kind}" + ;; + esac + + # Check query file + if [[ ! -f "$src_dir/$clean_name/$clean_name.ql" ]]; then + log_error "Query file not found: $src_dir/$clean_name/$clean_name.ql" + return 1 + fi + + # Check test files + if [[ ! -f "$test_dir/$clean_name/$clean_name.qlref" ]]; then + log_error "Test qlref file not found: $test_dir/$clean_name/$clean_name.qlref" + return 1 + fi + + # Verify qlref content + local expected_qlref="$clean_name/$clean_name.ql" + local actual_qlref=$(cat "$test_dir/$clean_name/$clean_name.qlref") + if [[ "$actual_qlref" != "$expected_qlref" ]]; then + log_error "qlref content mismatch for $language $kind. Expected: '$expected_qlref', Got: '$actual_qlref'" + return 1 + fi + + # Verify test source file exists and contains appropriate content for the language + local test_extensions=("py" "java" "js" "go" "cpp" "cs" "rb") + local test_found=false + + for ext in "${test_extensions[@]}"; do + if [[ -f "$test_dir/$clean_name/$clean_name.$ext" ]]; then + test_found=true + local test_content=$(cat "$test_dir/$clean_name/$clean_name.$ext") + + # Verify it's not CodeQL syntax (common mistake in templates) + if echo "$test_content" | grep -q "import.*ql\|from.*select"; then + log_error "Test file contains CodeQL syntax instead of $language source code" + return 1 + fi + + # Verify it contains some basic content + if [[ -z "$test_content" ]] || [[ ${#test_content} -lt 10 ]]; then + log_error "Test file appears to be empty or too short" + return 1 + fi + + break + fi + done + + if [[ "$test_found" != true ]]; then + log_warning "No test source file found for $language $kind query (this may be expected for some languages)" + fi + + # Verify the query has the correct @kind metadata + local query_content=$(cat "$src_dir/$clean_name/$clean_name.ql") + if ! echo "$query_content" | grep -q "@kind $kind"; then + log_error "Query $clean_name does not have correct @kind metadata. Expected: @kind $kind" + return 1 + fi + + log_success " ✓ $kind query validated for $language" + done + + # Run unit tests if enabled + if [[ "$RUN_TESTS" == "true" ]]; then + log_info " → Running unit tests for $language..." + if run_unit_tests_for_languages "$language" "$TEST_DIR"; then + log_success " ✓ Unit tests passed for $language" + else + log_error "Unit tests failed for $language" + return 1 + fi + else + log_info " → Skipping unit tests for $language (disabled)" + fi + + log_success "Language $language validation complete" + return 0 +} + +# Main validation function +main() { + # Parse command line arguments + parse_arguments "$@" + + log_info "Starting end-to-end CLI validation..." + log_info "Repository: $REPO_ROOT" + log_info "Test directory: $TEST_DIR" + log_info "Languages: ${LANGUAGES[*]}" + log_info "Query kinds: ${QUERY_KINDS[*]}" + log_info "Run tests: $RUN_TESTS" + + # Check if QLT binary exists + if [[ ! -f "$QLT_BINARY" ]]; then + log_error "QLT binary not found at: $QLT_BINARY" + log_error "Please build the project first: dotnet build" + exit 1 + fi + + # Setup test environment + setup_test_environment + + # Generate all queries first + if ! generate_all_queries; then + log_error "Failed to generate queries" + exit 1 + fi + + # Install all packs once + if ! install_all_packs; then + log_error "Failed to install packs" + exit 1 + fi + + # Track results + local passed=0 + local failed=0 + local failed_languages=() + + # Validate each language + for language in "${LANGUAGES[@]}"; do + if validate_language "$language"; then + ((passed++)) + else + ((failed++)) + failed_languages+=("$language") + fi + done + + echo + log_info "=== Validation Summary ===" + log_success "Number of Languages Passed: $passed" + if [[ $failed -gt 0 ]]; then + log_error "Number of Languages Failed: $failed" + if [[ ${#failed_languages[@]} -gt 0 ]]; then + log_error "Failed languages: ${failed_languages[*]}" + fi + else + log_success "All languages passed validation and unit testing." + fi + + if [[ $failed -eq 0 ]]; then + log_success "All validations passed! 🎉" + log_info "Generated files are available in: $TEST_DIR" + exit 0 + else + log_error "Some validations failed. 😞" + log_info "Generated files are available for troubleshooting in: $TEST_DIR" + exit 1 + fi +} + +# Run main function +main "$@" diff --git a/src/CodeQLToolkit.Core/Main.cs b/src/CodeQLToolkit.Core/Main.cs index b1e6442..054052f 100644 --- a/src/CodeQLToolkit.Core/Main.cs +++ b/src/CodeQLToolkit.Core/Main.cs @@ -21,7 +21,7 @@ public static async Task Main(string[] args) { Log.G().LogInformation("QLT Startup..."); - Console.OutputEncoding = System.Text.Encoding.UTF8; + Console.OutputEncoding = System.Text.Encoding.UTF8; var rootCommand = new RootCommand(); diff --git a/src/CodeQLToolkit.Features/Bundle/Commands/Targets/ValidateIntegrationTestResults.cs b/src/CodeQLToolkit.Features/Bundle/Commands/Targets/ValidateIntegrationTestResults.cs index 850d699..59cfac9 100644 --- a/src/CodeQLToolkit.Features/Bundle/Commands/Targets/ValidateIntegrationTestResults.cs +++ b/src/CodeQLToolkit.Features/Bundle/Commands/Targets/ValidateIntegrationTestResults.cs @@ -12,7 +12,7 @@ namespace CodeQLToolkit.Features.Bundle.Commands.Targets { public class ValidateIntegrationTestResults : CommandTarget { - public string Expected { get; set; } + public string Expected { get; set; } public string Actual { get; set; } public override void Run() @@ -25,7 +25,7 @@ public override void Run() DieWithError($"Expected file {Expected} does not exist."); } - if(!File.Exists(Actual)) + if (!File.Exists(Actual)) { DieWithError($"Actual file {Actual} does not exist."); } @@ -56,13 +56,13 @@ public override void Run() var actualDict = ExtractResultMap(actualSARIF); var expectedDict = ExtractResultMap(expectedSARIF); - + // Populate the differences. var resultsInExpectedNotInActual = FindMissingResults(expectedSARIF, actualDict); var resultsInActualNotInExpected = FindMissingResults(actualSARIF, expectedDict); // Report results. - if(resultsInExpectedNotInActual.Count == 0 && resultsInActualNotInExpected.Count == 0) + if (resultsInExpectedNotInActual.Count == 0 && resultsInActualNotInExpected.Count == 0) { Log.G().LogInformation($"SARIF results identical."); } @@ -129,20 +129,20 @@ List FindMissingResults(SARIFResult? expectedSARIF, Dictionary inResults) { - foreach(var result in inResults) + foreach (var result in inResults) { - if(ResultMatches(result, searchFor)) + if (ResultMatches(result, searchFor)) { return true; } } - + return false; } private bool ResultMatches(Result a, Result b) { - if(a.ruleId == b.ruleId && a.message.text == b.message.text && a.LocationsString() == b.LocationsString()) + if (a.ruleId == b.ruleId && a.message.text == b.message.text && a.LocationsString() == b.LocationsString()) { return true; } diff --git a/src/CodeQLToolkit.Features/Bundle/Lifecycle/BaseLifecycleTarget.cs b/src/CodeQLToolkit.Features/Bundle/Lifecycle/BaseLifecycleTarget.cs index b5e8f44..4cb60e8 100644 --- a/src/CodeQLToolkit.Features/Bundle/Lifecycle/BaseLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/Bundle/Lifecycle/BaseLifecycleTarget.cs @@ -8,7 +8,7 @@ namespace CodeQLToolkit.Features.Bundle.Lifecycle { abstract public class BaseLifecycleTarget : ILifecycleTarget { - public string UseRunner { get; set; } + public string UseRunner { get; set; } } } diff --git a/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/Actions/InitLifecycleTarget.cs b/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/Actions/InitLifecycleTarget.cs index 969c4d0..9127610 100644 --- a/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/Actions/InitLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/Actions/InitLifecycleTarget.cs @@ -22,7 +22,7 @@ public override void Run() // temporarily disable the language resolution var tmpLanguage = Language; Language = null; - + WriteTemplateIfOverwriteOrNotExists("install-qlt", Path.Combine(Base, ".github", "actions", "install-qlt", "action.yml"), "install-qlt action"); WriteTemplateIfOverwriteOrNotExists("run-bundle-integration-tests", Path.Combine(Base, ".github", "workflows", $"run-bundle-integration-tests-{tmpLanguage}.yml"), $"Run CodeQL Unit Tests ({Language})", new { diff --git a/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/GetEnabledCustomCodeQLBundlesLifecycleTarget.cs b/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/GetEnabledCustomCodeQLBundlesLifecycleTarget.cs index 31f20dc..5ce6bc9 100644 --- a/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/GetEnabledCustomCodeQLBundlesLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/GetEnabledCustomCodeQLBundlesLifecycleTarget.cs @@ -8,14 +8,14 @@ namespace CodeQLToolkit.Features.Bundle.Lifecycle.Targets { - public class GetEnabledCustomCodeQLBundlesLifecycleTarget : ILifecycleTarget - { + public class GetEnabledCustomCodeQLBundlesLifecycleTarget : ILifecycleTarget + { override public void Run() { Log.G().LogInformation("Running get enabled command..."); var c = new QLTConfig() - { + { Base = Base }; diff --git a/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/SetDisableCustomCodeQLBundlesLifecycleTarget.cs b/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/SetDisableCustomCodeQLBundlesLifecycleTarget.cs index 2a5162e..d449f31 100644 --- a/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/SetDisableCustomCodeQLBundlesLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/SetDisableCustomCodeQLBundlesLifecycleTarget.cs @@ -8,14 +8,14 @@ namespace CodeQLToolkit.Features.Bundle.Lifecycle.Targets { - public class SetDisableCustomCodeQLBundlesLifecycleTarget : ILifecycleTarget - { + public class SetDisableCustomCodeQLBundlesLifecycleTarget : ILifecycleTarget + { override public void Run() { Log.G().LogInformation("Running set command..."); var c = new QLTConfig() - { + { Base = Base }; diff --git a/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/SetEnableCustomCodeQLBundlesLifecycleTarget.cs b/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/SetEnableCustomCodeQLBundlesLifecycleTarget.cs index 09802da..5ba0a98 100644 --- a/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/SetEnableCustomCodeQLBundlesLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/Bundle/Lifecycle/Targets/SetEnableCustomCodeQLBundlesLifecycleTarget.cs @@ -8,14 +8,14 @@ namespace CodeQLToolkit.Features.Bundle.Lifecycle.Targets { - public class SetEnableCustomCodeQLBundlesLifecycleTarget : ILifecycleTarget - { + public class SetEnableCustomCodeQLBundlesLifecycleTarget : ILifecycleTarget + { override public void Run() { Log.G().LogInformation("Running set command..."); var c = new QLTConfig() - { + { Base = Base }; diff --git a/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/InstallCommand.cs b/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/InstallCommand.cs index ee7eb07..3d88e79 100644 --- a/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/InstallCommand.cs +++ b/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/InstallCommand.cs @@ -14,7 +14,7 @@ public class InstallCommand : CommandTarget { public bool CustomBundles { get; set; } public bool QuickBundles { get; set; } - public string[] Packs { get; set; } + public string[] Packs { get; set; } = Array.Empty(); void SetEnvironmentVariableMultiTarget(string name, string value) { @@ -22,24 +22,25 @@ void SetEnvironmentVariableMultiTarget(string name, string value) Environment.SetEnvironmentVariable(name, value); + // Only attempt to write to GITHUB_ENV if we're running in GitHub Actions if (AutomationTypeHelper.AutomationTypeFromString(AutomationTarget) == AutomationType.ACTIONS) { string? githubEnvPath = Environment.GetEnvironmentVariable("GITHUB_ENV"); try { - if (File.Exists(githubEnvPath)) + if (!string.IsNullOrEmpty(githubEnvPath) && File.Exists(githubEnvPath)) { File.AppendAllText(githubEnvPath, $"{name}={value}\n"); + Log.G().LogInformation($"Successfully wrote {name} to GITHUB_ENV file."); } else { - throw new Exception("Could not find GITHUB_ENV file."); + Log.G().LogWarning($"GITHUB_ENV file not found. Environment variable {name} set locally only."); } } - catch (Exception) + catch (Exception ex) { - Log.G().LogError($"Could not write to GITHUB_ENV file."); - throw; + Log.G().LogWarning($"Could not write to GITHUB_ENV file: {ex.Message}. Environment variable {name} set locally only."); } } } diff --git a/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/ListCommand.cs b/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/ListCommand.cs index e9cf658..a92a776 100644 --- a/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/ListCommand.cs +++ b/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/ListCommand.cs @@ -8,10 +8,10 @@ namespace CodeQLToolkit.Features.CodeQL.Commands.Targets { public class ListCommand : CommandTarget - { + { public override void Run() { - Log.G().LogInformation($"Running List Command"); - } + Log.G().LogInformation($"Running List Command"); + } } } diff --git a/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/UseCommand.cs b/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/UseCommand.cs index 31a37a1..be8ae26 100644 --- a/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/UseCommand.cs +++ b/src/CodeQLToolkit.Features/CodeQL/Commands/Targets/UseCommand.cs @@ -8,11 +8,11 @@ namespace CodeQLToolkit.Features.CodeQL.Commands.Targets { public class UseCommand : CommandTarget - { + { public override void Run() { Log.G().LogInformation($"Running Use command"); - - } + + } } } diff --git a/src/CodeQLToolkit.Features/CodeQL/Lifecycle/CodeQLLifecycleFeature.cs b/src/CodeQLToolkit.Features/CodeQL/Lifecycle/CodeQLLifecycleFeature.cs index 73e388f..ea35df5 100644 --- a/src/CodeQLToolkit.Features/CodeQL/Lifecycle/CodeQLLifecycleFeature.cs +++ b/src/CodeQLToolkit.Features/CodeQL/Lifecycle/CodeQLLifecycleFeature.cs @@ -53,7 +53,7 @@ public void Register(Command parentCommand) { - getVersionCommand.SetHandler((basePath) => + getVersionCommand.SetHandler((basePath) => { Log.G().LogInformation("Executing get command..."); diff --git a/src/CodeQLToolkit.Features/CodeQL/Lifecycle/Targets/SetVersionLifecycleTarget.cs b/src/CodeQLToolkit.Features/CodeQL/Lifecycle/Targets/SetVersionLifecycleTarget.cs index 9025b71..49ba2df 100644 --- a/src/CodeQLToolkit.Features/CodeQL/Lifecycle/Targets/SetVersionLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/CodeQL/Lifecycle/Targets/SetVersionLifecycleTarget.cs @@ -8,7 +8,7 @@ namespace CodeQLToolkit.Features.CodeQL.Lifecycle.Targets { - public class SetVersionLifecycleTarget : ILifecycleTarget + public class SetVersionLifecycleTarget : ILifecycleTarget { public string CodeQLCLI { get; set; } public string CodeQLStandardLibrary { get; set; } diff --git a/src/CodeQLToolkit.Features/CodeQLToolkit.Features.csproj b/src/CodeQLToolkit.Features/CodeQLToolkit.Features.csproj index ed75ed1..f304a62 100644 --- a/src/CodeQLToolkit.Features/CodeQLToolkit.Features.csproj +++ b/src/CodeQLToolkit.Features/CodeQLToolkit.Features.csproj @@ -51,6 +51,69 @@ Always + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + Always @@ -72,6 +135,48 @@ Always + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + Always diff --git a/src/CodeQLToolkit.Features/Pack/Commands/PackCommandFeature.cs b/src/CodeQLToolkit.Features/Pack/Commands/PackCommandFeature.cs index d01968b..1c7dc1d 100644 --- a/src/CodeQLToolkit.Features/Pack/Commands/PackCommandFeature.cs +++ b/src/CodeQLToolkit.Features/Pack/Commands/PackCommandFeature.cs @@ -31,17 +31,20 @@ public void Register(Command parentCommand) runCommand.Add(sayHello); runCommand.Add(sayGoodbye); - sayHello.SetHandler((basePath, times) => { + sayHello.SetHandler((basePath, times) => + { - new HelloJeongsooCommandTarget() { + new HelloJeongsooCommandTarget() + { Base = basePath, - Times = times - + Times = times + }.Run(); }, Globals.BasePathOption, howManyTimesHello); - sayGoodbye.SetHandler((basePath, times) => { + sayGoodbye.SetHandler((basePath, times) => + { Console.WriteLine($"Saying goodbye {times} number of times"); diff --git a/src/CodeQLToolkit.Features/Query/Commands/QueryCommandFeature.cs b/src/CodeQLToolkit.Features/Query/Commands/QueryCommandFeature.cs index ff44aa2..27ceade 100644 --- a/src/CodeQLToolkit.Features/Query/Commands/QueryCommandFeature.cs +++ b/src/CodeQLToolkit.Features/Query/Commands/QueryCommandFeature.cs @@ -15,7 +15,7 @@ public class QueryCommandFeature : FeatureBase, IToolkitCommandFeature { public QueryCommandFeature() { - FeatureName = "Query"; + FeatureName = "Query"; } public void Register(Command parentCommand) @@ -31,7 +31,7 @@ public void Register(Command parentCommand) runCommand.Add(installPacksQueryCommand); installPacksQueryCommand.SetHandler( - (basePath, useBundle) => new InstallQueryPacksCommandTarget() { Base = basePath, UseBundle = useBundle}.Run(), Globals.BasePathOption, Globals.UseBundle); + (basePath, useBundle) => new InstallQueryPacksCommandTarget() { Base = basePath, UseBundle = useBundle }.Run(), Globals.BasePathOption, Globals.UseBundle); } diff --git a/src/CodeQLToolkit.Features/Query/Commands/Targets/InstallQueryPacksCommandTarget.cs b/src/CodeQLToolkit.Features/Query/Commands/Targets/InstallQueryPacksCommandTarget.cs index b38d2bc..085102f 100644 --- a/src/CodeQLToolkit.Features/Query/Commands/Targets/InstallQueryPacksCommandTarget.cs +++ b/src/CodeQLToolkit.Features/Query/Commands/Targets/InstallQueryPacksCommandTarget.cs @@ -33,23 +33,23 @@ public override void Run() // filter the packs that are part of a custom bundle if we are using bundles. - if(UseBundle) + if (UseBundle) { // load the config var config = QLTConfig.LoadFromFile(Base); Log.G().LogInformation("In bundle mode so filtering bundled packs..."); - + foreach (var pack in config.CodeQLPackConfiguration) { Log.G().LogInformation($"Pack {pack.Name} will NOT installed because it is part of the bundle..."); } - files = files.Where(f => + files = files.Where(f => // all things that are part of the customization pack must be excluded. // if it is exported is not relevant here. - !config.CodeQLPackConfiguration.Any(p => CodeQLPackReader.read(f).Name == p.Name && (p.Bundle==true || p.ReferencesBundle==true)) + !config.CodeQLPackConfiguration.Any(p => CodeQLPackReader.read(f).Name == p.Name && (p.Bundle == true || p.ReferencesBundle == true)) ).ToArray(); Log.G().LogInformation($"Got {files.Length} packs after filtering..."); @@ -61,11 +61,11 @@ public override void Run() } - foreach ( string file in files ) + foreach (string file in files) { Log.G().LogInformation($"Installing qlpack {file}..."); - using(Process process = new Process()) + using (Process process = new Process()) { process.StartInfo.FileName = installation.CodeQLToolBinary; process.StartInfo.UseShellExecute = false; @@ -75,7 +75,7 @@ public override void Run() process.WaitForExit(); - if(process.ExitCode !=0) + if (process.ExitCode != 0) { DieWithError($"Failed to install query pack {file}."); } diff --git a/src/CodeQLToolkit.Features/Query/Lifecycle/QueryLifecycleFeature.cs b/src/CodeQLToolkit.Features/Query/Lifecycle/QueryLifecycleFeature.cs index 2b725e0..51d222e 100644 --- a/src/CodeQLToolkit.Features/Query/Lifecycle/QueryLifecycleFeature.cs +++ b/src/CodeQLToolkit.Features/Query/Lifecycle/QueryLifecycleFeature.cs @@ -34,7 +34,7 @@ public void Register(Command parentCommand) { Log.G().LogInformation("Executing init command..."); - + new InitLifecycleTarget() { FeatureName = FeatureName, @@ -43,7 +43,7 @@ public void Register(Command parentCommand) }.Run(); - + }, Globals.BasePathOption, overwriteExistingOption); } diff --git a/src/CodeQLToolkit.Features/Query/Scaffolding/QueryScaffoldFeature.cs b/src/CodeQLToolkit.Features/Query/Scaffolding/QueryScaffoldFeature.cs index 7c30af0..682b4ff 100644 --- a/src/CodeQLToolkit.Features/Query/Scaffolding/QueryScaffoldFeature.cs +++ b/src/CodeQLToolkit.Features/Query/Scaffolding/QueryScaffoldFeature.cs @@ -3,6 +3,7 @@ using CodeQLToolkit.Shared.Options; using CodeQLToolkit.Shared.Utils; using System.CommandLine; +using System.CommandLine.Invocation; namespace CodeQLToolkit.Features.Query.Scaffolding { @@ -12,7 +13,16 @@ public QueryScaffoldFeature() { FeatureName = "Query"; } - public override LanguageType[] SupportedLangauges => new LanguageType[] { LanguageType.C, LanguageType.CPP, LanguageType.JAVASCRIPT }; + public override LanguageType[] SupportedLangauges => new LanguageType[] { + LanguageType.C, + LanguageType.CPP, + LanguageType.CSHARP, + LanguageType.GO, + LanguageType.JAVA, + LanguageType.JAVASCRIPT, + LanguageType.PYTHON, + LanguageType.RUBY + }; public void Register(Command parentCommand) { @@ -32,7 +42,9 @@ public void Register(Command parentCommand) var queryLanguageOption = new Option("--language", $"The language to generate a query for.") { IsRequired = true } .FromAmong(SupportedLangauges.Select(x => x.ToOptionString()).ToArray()); var queryPackOption = new Option("--pack", "The name of the query pack to place this query in.") { IsRequired = true }; - var queryPackScopeOption = new Option("--scope", "The scope to use") { IsRequired = true }; + var queryPackScopeOption = new Option("--scope", "The scope to use (optional)") { IsRequired = false }; + var queryKindOption = new Option("--query-kind", () => "problem", "The kind of query to generate (problem or path-problem).") { IsRequired = false } + .FromAmong(new[] { "problem", "path-problem" }); newQueryCommand.Add(createQueryPackOption); newQueryCommand.Add(createTestsOption); @@ -41,12 +53,22 @@ public void Register(Command parentCommand) newQueryCommand.Add(queryPackOption); newQueryCommand.Add(overwriteExistingOption); newQueryCommand.Add(queryPackScopeOption); + newQueryCommand.Add(queryKindOption); scaffoldCommand.Add(newQueryCommand); { - newQueryCommand.SetHandler((createQueryPack, createTests, queryName, queryLangauge, queryPack, basePath, overwriteExisting, queryPackScope) => + newQueryCommand.SetHandler((InvocationContext context) => { + var createQueryPack = context.ParseResult.GetValueForOption(createQueryPackOption); + var createTests = context.ParseResult.GetValueForOption(createTestsOption); + var queryName = context.ParseResult.GetValueForOption(queryNameOption); + var queryLangauge = context.ParseResult.GetValueForOption(queryLanguageOption); + var queryPack = context.ParseResult.GetValueForOption(queryPackOption); + var basePath = context.ParseResult.GetValueForOption(Globals.BasePathOption); + var overwriteExisting = context.ParseResult.GetValueForOption(overwriteExistingOption); + var queryPackScope = context.ParseResult.GetValueForOption(queryPackScopeOption); + var queryKind = context.ParseResult.GetValueForOption(queryKindOption); if (!IsSupportedLangauge(queryLangauge)) { @@ -63,10 +85,11 @@ public void Register(Command parentCommand) CreateTests = createTests, CreateQueryPack = createQueryPack, OverwriteExisting = overwriteExisting, - FeatureName = FeatureName + FeatureName = FeatureName, + QueryKind = queryKind }.Run(); - }, createQueryPackOption, createTestsOption, queryNameOption, queryLanguageOption, queryPackOption, Globals.BasePathOption, overwriteExistingOption, queryPackScopeOption); + }); } } diff --git a/src/CodeQLToolkit.Features/Query/Scaffolding/Targets/NewQueryScaffoldTarget.cs b/src/CodeQLToolkit.Features/Query/Scaffolding/Targets/NewQueryScaffoldTarget.cs index 36f8fa3..c0b69c1 100644 --- a/src/CodeQLToolkit.Features/Query/Scaffolding/Targets/NewQueryScaffoldTarget.cs +++ b/src/CodeQLToolkit.Features/Query/Scaffolding/Targets/NewQueryScaffoldTarget.cs @@ -10,11 +10,13 @@ public class NewQueryScaffoldTarget : ScaffoldTarget public bool CreateTests { get; set; } public bool CreateQueryPack { get; set; } public string QueryPackScope { get; set; } + public string QueryKind { get; set; } public override void Run() { Log.G().LogInformation("Creating new query..."); + // Use the provided query name exactly as given var query = new Shared.Utils.Query() { Language = Language, @@ -23,16 +25,26 @@ public override void Run() Scope = QueryPackScope, Base = Base }; - + Directory.CreateDirectory(query.QueryFileDir); - WriteTemplateIfOverwriteOrNotExists("new-query", query.QueryFilePath, "new query", new + // Select template based on query kind + string templateName = QueryKind.ToLowerInvariant() switch + { + "path-problem" => "new-dataflow-query", + "problem" => "new-query", + _ => "new-query" + }; + + WriteTemplateIfOverwriteOrNotExists(templateName, query.QueryFilePath, "new query", new { - language = query.Language, + language = query.Language.ToDirectory(), // Use directory name instead of enum value queryPackName = query.QueryPackName, + query_pack_name = query.QueryPackName, // Add snake_case version for template compatibility queryName = query.Name, + query_name = query.Name, // Add snake_case version for template compatibility description = "Replace this text with a description of your query.", - qlLanguageImport = query.GetLanguageImportForLangauge() + ql_language_import = query.GetLanguageImportForLanguage() }); if (CreateQueryPack) @@ -40,30 +52,46 @@ public override void Run() WriteTemplateIfOverwriteOrNotExists("qlpack-query", query.QueryPackPath, "new query pack", new { queryPackScope = query.Scope, - queryPackName = query.QueryPackName + queryPackName = query.QueryPackName, + query_pack_full_name = string.IsNullOrEmpty(query.Scope) ? query.QueryPackName : $"{query.Scope}/{query.QueryPackName}", + ql_language = query.Language.ToDirectory() }); } - + if (CreateTests) - { + { Directory.CreateDirectory(query.QueryFileTestDir); // the source file to use - WriteTemplateIfOverwriteOrNotExists("test", query.QueryFileTestPath, "new query test file", new {}); + WriteTemplateIfOverwriteOrNotExists("test", query.QueryFileTestPath, "new query test file", new + { + queryName = query.Name, + query_name = query.Name, + query_kind = QueryKind, + test_file_prefix = query.Name + }); // the expected file - WriteTemplateIfOverwriteOrNotExists("expected", query.QueryTestExpectedFile, "new query test expected file", new { }); + WriteTemplateIfOverwriteOrNotExists("expected", query.QueryTestExpectedFile, "new query test expected file", new + { + query_kind = QueryKind, + test_file_prefix = query.Name + }); // the the qlref file - WriteTemplateIfOverwriteOrNotExists("testref", query.QueryFileQLRefPath, "new query test ref", new { + WriteTemplateIfOverwriteOrNotExists("testref", query.QueryFileQLRefPath, "new query test ref", new + { queryName = query.Name }); // the qlpack file - WriteTemplateIfOverwriteOrNotExists("qlpack-test", query.QueryPackTestPath, "new query test pack", new { - queryPackDependency = $"{query.Scope}/{query.QueryPackName}", + WriteTemplateIfOverwriteOrNotExists("qlpack-test", query.QueryPackTestPath, "new query test pack", new + { + queryPackDependency = string.IsNullOrEmpty(query.Scope) ? query.QueryPackName : $"{query.Scope}/{query.QueryPackName}", queryPackScope = query.Scope, - queryPackName = query.QueryTestPackName + queryPackName = query.QueryTestPackName, + query_pack_full_name = string.IsNullOrEmpty(query.Scope) ? query.QueryTestPackName : $"{query.Scope}/{query.QueryTestPackName}", + ql_language = query.Language.ToDirectory() }); } diff --git a/src/CodeQLToolkit.Features/Templates/Query/cpp/expected.liquid b/src/CodeQLToolkit.Features/Templates/Query/cpp/expected.liquid index 7962166..79b5a27 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/cpp/expected.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/cpp/expected.liquid @@ -1 +1,8 @@ -// not implemented \ No newline at end of file +{% if query_kind == "path-problem" %} +{% comment %}Path-problem queries with none() source and sink return no results{% endcomment %} +{% else %} +{% comment %}Problem queries that select all expressions return results for each expression{% endcomment %} +| {{ test_file_prefix }}.cpp:2:5:2:7 | Replace this with your query. | +| {{ test_file_prefix }}.cpp:4:5:4:12 | Replace this with your query. | +| {{ test_file_prefix }}.cpp:4:12:4:13 | Replace this with your query. | +{% endif %} \ No newline at end of file diff --git a/src/CodeQLToolkit.Features/Templates/Query/cpp/new-dataflow-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/cpp/new-dataflow-query.liquid index 5f28270..28d1c32 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/cpp/new-dataflow-query.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/cpp/new-dataflow-query.liquid @@ -1 +1,31 @@ - \ No newline at end of file +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind path-problem + * @precision high + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} +import semmle.code.cpp.ir.dataflow.TaintTracking +import Flow::PathGraph + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // Define your sources here + none() + } + + predicate isSink(DataFlow::Node sink) { + // Define your sinks here + none() + } +} + +module Flow = TaintTracking::Global; + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "Replace this with your dataflow query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/cpp/new-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/cpp/new-query.liquid index f5c42c4..2695fbb 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/cpp/new-query.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/cpp/new-query.liquid @@ -1,5 +1,5 @@ /** - * @id {{language}}/{{query_pack_name}}/{{query_name}} + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} * @name {{query_name}} * @description {{description}} * @kind problem @@ -8,7 +8,7 @@ * @tags {{query_pack_name}} */ - import {{ql_language_import}} - - from Expr e - select e, "Replace this with your query." \ No newline at end of file +import {{ql_language_import}} + +from File f +select f, "Replace this with your query." \ No newline at end of file diff --git a/src/CodeQLToolkit.Features/Templates/Query/cpp/qlpack-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/cpp/qlpack-query.liquid index c3a1a45..894150b 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/cpp/qlpack-query.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/cpp/qlpack-query.liquid @@ -1,7 +1,5 @@ -name: {{query_pack_scope}}/{{query_pack_name}} +name: {{query_pack_full_name}} version: 0.0.0 -description: Default description -suites: -license: +library: false dependencies: - codeql/cpp-all: 0.3.5 \ No newline at end of file + codeql/{{ql_language}}-all: "*" \ No newline at end of file diff --git a/src/CodeQLToolkit.Features/Templates/Query/cpp/qlpack-test.liquid b/src/CodeQLToolkit.Features/Templates/Query/cpp/qlpack-test.liquid index 560a515..001595a 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/cpp/qlpack-test.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/cpp/qlpack-test.liquid @@ -1,4 +1,4 @@ -name: {{query_pack_scope}}/{{query_pack_name}} +name: {{query_pack_full_name}} version: 0.0.0 description: Default description suites: diff --git a/src/CodeQLToolkit.Features/Templates/Query/cpp/test.liquid b/src/CodeQLToolkit.Features/Templates/Query/cpp/test.liquid index f39be51..e87f47a 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/cpp/test.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/cpp/test.liquid @@ -1 +1,5 @@ -// replace with your test file \ No newline at end of file +// Test case for {{query_name}} +int main() { + // ${{query_name}} + return 0; +} \ No newline at end of file diff --git a/src/CodeQLToolkit.Features/Templates/Query/csharp/expected.liquid b/src/CodeQLToolkit.Features/Templates/Query/csharp/expected.liquid new file mode 100644 index 0000000..a6fc390 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/csharp/expected.liquid @@ -0,0 +1,7 @@ +{% if query_kind == "path-problem" %} +{% comment %}Path-problem queries with none() source and sink return no results{% endcomment %} +{% else %} +{% comment %}Problem queries that select all expressions return results for each expression{% endcomment %} +| {{ test_file_prefix }}.cs:7:9:7:11 | Replace this with your query. | +| {{ test_file_prefix }}.cs:9:5:9:16 | Replace this with your query. | +{% endif %} diff --git a/src/CodeQLToolkit.Features/Templates/Query/csharp/new-dataflow-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/csharp/new-dataflow-query.liquid new file mode 100644 index 0000000..4c8f04c --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/csharp/new-dataflow-query.liquid @@ -0,0 +1,30 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind path-problem + * @precision high + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // Define your sources here + none() + } + + predicate isSink(DataFlow::Node sink) { + // Define your sinks here + none() + } +} + +module Flow = DataFlow::Global; +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "Replace this with your dataflow query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/csharp/new-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/csharp/new-query.liquid new file mode 100644 index 0000000..a12fb15 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/csharp/new-query.liquid @@ -0,0 +1,14 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind problem + * @precision medium + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} + +from Expr e +select e, "Replace this with your query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/csharp/qlpack-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/csharp/qlpack-query.liquid new file mode 100644 index 0000000..349cdbf --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/csharp/qlpack-query.liquid @@ -0,0 +1,5 @@ +name: {{query_pack_full_name}} +version: 0.0.0 +library: false +dependencies: + codeql/{{ql_language}}-all: "*" diff --git a/src/CodeQLToolkit.Features/Templates/Query/csharp/qlpack-test.liquid b/src/CodeQLToolkit.Features/Templates/Query/csharp/qlpack-test.liquid new file mode 100644 index 0000000..8b1ac5b --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/csharp/qlpack-test.liquid @@ -0,0 +1,8 @@ +name: {{query_pack_full_name}} +version: 0.0.0 +description: Default description +suites: +license: +extractor: csharp +dependencies: + {{query_pack_dependency}}: '*' diff --git a/src/CodeQLToolkit.Features/Templates/Query/csharp/test.liquid b/src/CodeQLToolkit.Features/Templates/Query/csharp/test.liquid new file mode 100644 index 0000000..9b7c1a0 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/csharp/test.liquid @@ -0,0 +1,10 @@ +// Test case for {{query_name}} +using System; + +public class {{query_name}} +{ + public static void Main(string[] args) + { + // ${{query_name}} + } +} diff --git a/src/CodeQLToolkit.Features/Templates/Query/csharp/testref.liquid b/src/CodeQLToolkit.Features/Templates/Query/csharp/testref.liquid new file mode 100644 index 0000000..24b07aa --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/csharp/testref.liquid @@ -0,0 +1 @@ +{{query_name}}/{{query_name}}.ql diff --git a/src/CodeQLToolkit.Features/Templates/Query/go/expected.liquid b/src/CodeQLToolkit.Features/Templates/Query/go/expected.liquid new file mode 100644 index 0000000..8c9efae --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/go/expected.liquid @@ -0,0 +1,7 @@ +{% if query_kind == "path-problem" %} +{% comment %}Path-problem queries with none() source and sink return no results{% endcomment %} +{% else %} +{% comment %}Problem queries that select all expressions return results for each expression{% endcomment %} +| {{ test_file_prefix }}.go:4:5:4:17 | Replace this with your query. | +| {{ test_file_prefix }}.go:4:10:4:17 | Replace this with your query. | +{% endif %} diff --git a/src/CodeQLToolkit.Features/Templates/Query/go/new-dataflow-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/go/new-dataflow-query.liquid new file mode 100644 index 0000000..4c8f04c --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/go/new-dataflow-query.liquid @@ -0,0 +1,30 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind path-problem + * @precision high + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // Define your sources here + none() + } + + predicate isSink(DataFlow::Node sink) { + // Define your sinks here + none() + } +} + +module Flow = DataFlow::Global; +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "Replace this with your dataflow query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/go/new-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/go/new-query.liquid new file mode 100644 index 0000000..363fc99 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/go/new-query.liquid @@ -0,0 +1,29 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind problem + * @precision medium + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // Define your sources here + none() + } + + predicate isSink(DataFlow::Node sink) { + // Define your sinks here + none() + } +} + +module Flow = DataFlow::Global; + +from DataFlow::Node source, string msg +where Flow::flow(source, _) and msg = "Replace this with your query." +select source, msg diff --git a/src/CodeQLToolkit.Features/Templates/Query/go/qlpack-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/go/qlpack-query.liquid new file mode 100644 index 0000000..349cdbf --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/go/qlpack-query.liquid @@ -0,0 +1,5 @@ +name: {{query_pack_full_name}} +version: 0.0.0 +library: false +dependencies: + codeql/{{ql_language}}-all: "*" diff --git a/src/CodeQLToolkit.Features/Templates/Query/go/qlpack-test.liquid b/src/CodeQLToolkit.Features/Templates/Query/go/qlpack-test.liquid new file mode 100644 index 0000000..2845096 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/go/qlpack-test.liquid @@ -0,0 +1,6 @@ +name: {{query_pack_name}} +version: 0.0.0 +dependencies: + codeql/{{ql_language}}-all: "*" + {{query_pack_dependency}}: "*" +extractor: {{ql_language}} diff --git a/src/CodeQLToolkit.Features/Templates/Query/go/test.liquid b/src/CodeQLToolkit.Features/Templates/Query/go/test.liquid new file mode 100644 index 0000000..42fbeae --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/go/test.liquid @@ -0,0 +1,6 @@ +// Test case for {{query_name}} +package main + +func main() { + // ${{query_name}} +} diff --git a/src/CodeQLToolkit.Features/Templates/Query/go/testref.liquid b/src/CodeQLToolkit.Features/Templates/Query/go/testref.liquid new file mode 100644 index 0000000..24b07aa --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/go/testref.liquid @@ -0,0 +1 @@ +{{query_name}}/{{query_name}}.ql diff --git a/src/CodeQLToolkit.Features/Templates/Query/java/expected.liquid b/src/CodeQLToolkit.Features/Templates/Query/java/expected.liquid new file mode 100644 index 0000000..c430a7d --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/java/expected.liquid @@ -0,0 +1,7 @@ +{% if query_kind == "path-problem" %} +{% comment %}Path-problem queries with none() source and sink return no results{% endcomment %} +{% else %} +{% comment %}Problem queries that select all expressions return results for each expression{% endcomment %} +| {{ test_file_prefix }}.java:4:9:4:11 | Replace this with your query. | +| {{ test_file_prefix }}.java:6:5:6:16 | Replace this with your query. | +{% endif %} diff --git a/src/CodeQLToolkit.Features/Templates/Query/java/new-dataflow-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/java/new-dataflow-query.liquid new file mode 100644 index 0000000..19972d5 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/java/new-dataflow-query.liquid @@ -0,0 +1,31 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind path-problem + * @precision medium + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} +import semmle.code.java.dataflow.DataFlow + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { + // Define your source here + none() + } + + predicate isSink(DataFlow::Node node) { + // Define your sink here + none() + } +} + +module Flow = DataFlow::Global; +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "Replace this with your query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/java/new-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/java/new-query.liquid new file mode 100644 index 0000000..4f6ba5a --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/java/new-query.liquid @@ -0,0 +1,30 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind problem + * @precision medium + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} +import semmle.code.java.dataflow.DataFlow + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // Define your sources here + none() + } + + predicate isSink(DataFlow::Node sink) { + // Define your sinks here + none() + } +} + +module Flow = DataFlow::Global; + +from DataFlow::Node source, string msg +where Flow::flow(source, _) and msg = "Replace this with your query." +select source, msg diff --git a/src/CodeQLToolkit.Features/Templates/Query/java/qlpack-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/java/qlpack-query.liquid new file mode 100644 index 0000000..349cdbf --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/java/qlpack-query.liquid @@ -0,0 +1,5 @@ +name: {{query_pack_full_name}} +version: 0.0.0 +library: false +dependencies: + codeql/{{ql_language}}-all: "*" diff --git a/src/CodeQLToolkit.Features/Templates/Query/java/qlpack-test.liquid b/src/CodeQLToolkit.Features/Templates/Query/java/qlpack-test.liquid new file mode 100644 index 0000000..2845096 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/java/qlpack-test.liquid @@ -0,0 +1,6 @@ +name: {{query_pack_name}} +version: 0.0.0 +dependencies: + codeql/{{ql_language}}-all: "*" + {{query_pack_dependency}}: "*" +extractor: {{ql_language}} diff --git a/src/CodeQLToolkit.Features/Templates/Query/java/test.liquid b/src/CodeQLToolkit.Features/Templates/Query/java/test.liquid new file mode 100644 index 0000000..488a88e --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/java/test.liquid @@ -0,0 +1,6 @@ +// Test case for {{query_name}} +public class {{query_name}} { + public static void main(String[] args) { + // ${{query_name}} + } +} diff --git a/src/CodeQLToolkit.Features/Templates/Query/java/testref.liquid b/src/CodeQLToolkit.Features/Templates/Query/java/testref.liquid new file mode 100644 index 0000000..24b07aa --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/java/testref.liquid @@ -0,0 +1 @@ +{{query_name}}/{{query_name}}.ql diff --git a/src/CodeQLToolkit.Features/Templates/Query/javascript/expected.liquid b/src/CodeQLToolkit.Features/Templates/Query/javascript/expected.liquid index 7962166..58c7de8 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/javascript/expected.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/javascript/expected.liquid @@ -1 +1,8 @@ -// not implemented \ No newline at end of file +{% if query_kind == "path-problem" %} +{% comment %}Path-problem queries with none() source and sink return no results{% endcomment %} +{% else %} +{% comment %}Problem queries that select all expressions return results for each expression{% endcomment %} +| {{ test_file_prefix }}.js:2:5:2:12 | Replace this with your query. | +| {{ test_file_prefix }}.js:6:5:6:17 | Replace this with your query. | +| {{ test_file_prefix }}.js:6:10:6:17 | Replace this with your query. | +{% endif %} \ No newline at end of file diff --git a/src/CodeQLToolkit.Features/Templates/Query/javascript/new-dataflow-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/javascript/new-dataflow-query.liquid index 5f28270..76f6683 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/javascript/new-dataflow-query.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/javascript/new-dataflow-query.liquid @@ -1 +1,30 @@ - \ No newline at end of file +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind path-problem + * @precision high + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} +import DataFlow::PathGraph + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + // Define your sources here + none() + } + + predicate isSink(DataFlow::Node sink) { + // Define your sinks here + none() + } +} + +module Flow = TaintTracking::Global; + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "Replace this with your dataflow query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/javascript/new-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/javascript/new-query.liquid index f5c42c4..2695fbb 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/javascript/new-query.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/javascript/new-query.liquid @@ -1,5 +1,5 @@ /** - * @id {{language}}/{{query_pack_name}}/{{query_name}} + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} * @name {{query_name}} * @description {{description}} * @kind problem @@ -8,7 +8,7 @@ * @tags {{query_pack_name}} */ - import {{ql_language_import}} - - from Expr e - select e, "Replace this with your query." \ No newline at end of file +import {{ql_language_import}} + +from File f +select f, "Replace this with your query." \ No newline at end of file diff --git a/src/CodeQLToolkit.Features/Templates/Query/javascript/qlpack-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/javascript/qlpack-query.liquid index 8120bfc..894150b 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/javascript/qlpack-query.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/javascript/qlpack-query.liquid @@ -1,7 +1,5 @@ -name: {{query_pack_scope}}/{{query_pack_name}} +name: {{query_pack_full_name}} version: 0.0.0 -description: Default description -suites: -license: +library: false dependencies: - codeql/javascript-all: "^0.6.3" \ No newline at end of file + codeql/{{ql_language}}-all: "*" \ No newline at end of file diff --git a/src/CodeQLToolkit.Features/Templates/Query/javascript/qlpack-test.liquid b/src/CodeQLToolkit.Features/Templates/Query/javascript/qlpack-test.liquid index 515d065..6d290a3 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/javascript/qlpack-test.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/javascript/qlpack-test.liquid @@ -1,4 +1,4 @@ -name: {{query_pack_scope}}/{{query_pack_name}} +name: {{query_pack_full_name}} version: 0.0.0 description: Default description suites: diff --git a/src/CodeQLToolkit.Features/Templates/Query/javascript/test.liquid b/src/CodeQLToolkit.Features/Templates/Query/javascript/test.liquid index f39be51..217c19c 100644 --- a/src/CodeQLToolkit.Features/Templates/Query/javascript/test.liquid +++ b/src/CodeQLToolkit.Features/Templates/Query/javascript/test.liquid @@ -1 +1,8 @@ -// replace with your test file \ No newline at end of file +// Test case for {{query_name}} +function main() { + // ${{query_name}} +} + +if (require.main === module) { + main(); +} \ No newline at end of file diff --git a/src/CodeQLToolkit.Features/Templates/Query/python/expected.liquid b/src/CodeQLToolkit.Features/Templates/Query/python/expected.liquid new file mode 100644 index 0000000..f271c3c --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/python/expected.liquid @@ -0,0 +1,16 @@ +{% if query_kind == "path-problem" %} +{% comment %}Path-problem queries with none() source and sink return no results{% endcomment %} +{% else %} +{% comment %}Problem queries that select all expressions return results for each expression{% endcomment %} +| {{ test_file_prefix }}.py:1:5:1:7 | Replace this with your query. | +| {{ test_file_prefix }}.py:2:5:2:12 | Replace this with your query. | +| {{ test_file_prefix }}.py:2:9:2:12 | Replace this with your query. | +| {{ test_file_prefix }}.py:3:5:3:18 | Replace this with your query. | +| {{ test_file_prefix }}.py:3:9:3:10 | Replace this with your query. | +| {{ test_file_prefix }}.py:3:13:3:18 | Replace this with your query. | +| {{ test_file_prefix }}.py:3:17:3:18 | Replace this with your query. | +| {{ test_file_prefix }}.py:4:5:4:12 | Replace this with your query. | +| {{ test_file_prefix }}.py:4:12:4:13 | Replace this with your query. | +| {{ test_file_prefix }}.py:5:1:5:13 | Replace this with your query. | +| {{ test_file_prefix }}.py:5:6:5:13 | Replace this with your query. | +{% endif %} diff --git a/src/CodeQLToolkit.Features/Templates/Query/python/new-dataflow-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/python/new-dataflow-query.liquid new file mode 100644 index 0000000..98ca19a --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/python/new-dataflow-query.liquid @@ -0,0 +1,33 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind path-problem + * @problem.severity error + * @security-severity 6.1 + * @precision high + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.TaintTracking + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { + // Define your source here + none() + } + + predicate isSink(DataFlow::Node node) { + // Define your sink here + none() + } +} + +module Flow = TaintTracking::Global; +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "Replace this with your query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/python/new-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/python/new-query.liquid new file mode 100644 index 0000000..a12fb15 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/python/new-query.liquid @@ -0,0 +1,14 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind problem + * @precision medium + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} + +from Expr e +select e, "Replace this with your query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/python/qlpack-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/python/qlpack-query.liquid new file mode 100644 index 0000000..349cdbf --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/python/qlpack-query.liquid @@ -0,0 +1,5 @@ +name: {{query_pack_full_name}} +version: 0.0.0 +library: false +dependencies: + codeql/{{ql_language}}-all: "*" diff --git a/src/CodeQLToolkit.Features/Templates/Query/python/qlpack-test.liquid b/src/CodeQLToolkit.Features/Templates/Query/python/qlpack-test.liquid new file mode 100644 index 0000000..2845096 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/python/qlpack-test.liquid @@ -0,0 +1,6 @@ +name: {{query_pack_name}} +version: 0.0.0 +dependencies: + codeql/{{ql_language}}-all: "*" + {{query_pack_dependency}}: "*" +extractor: {{ql_language}} diff --git a/src/CodeQLToolkit.Features/Templates/Query/python/test.liquid b/src/CodeQLToolkit.Features/Templates/Query/python/test.liquid new file mode 100644 index 0000000..d94d2d9 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/python/test.liquid @@ -0,0 +1,6 @@ +# Test file for {{ query_name }} query +x = 42 +y = "hello" +z = x + len(y) +result = x + 1 +print(result) diff --git a/src/CodeQLToolkit.Features/Templates/Query/python/testref.liquid b/src/CodeQLToolkit.Features/Templates/Query/python/testref.liquid new file mode 100644 index 0000000..24b07aa --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/python/testref.liquid @@ -0,0 +1 @@ +{{query_name}}/{{query_name}}.ql diff --git a/src/CodeQLToolkit.Features/Templates/Query/ruby/expected.liquid b/src/CodeQLToolkit.Features/Templates/Query/ruby/expected.liquid new file mode 100644 index 0000000..044f005 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/ruby/expected.liquid @@ -0,0 +1,8 @@ +{% if query_kind == "path-problem" %} +{% comment %}Path-problem queries with none() source and sink return no results{% endcomment %} +{% else %} +{% comment %}Problem queries that select all expressions return results for each expression{% endcomment %} +| {{ test_file_prefix }}.rb:2:3:2:5 | Replace this with your query. | +| {{ test_file_prefix }}.rb:5:1:5:19 | Replace this with your query. | +| {{ test_file_prefix }}.rb:5:6:5:19 | Replace this with your query. | +{% endif %} diff --git a/src/CodeQLToolkit.Features/Templates/Query/ruby/new-dataflow-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/ruby/new-dataflow-query.liquid new file mode 100644 index 0000000..d4da9a7 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/ruby/new-dataflow-query.liquid @@ -0,0 +1,32 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind path-problem + * @precision medium + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} +import codeql.ruby.AST +import codeql.ruby.DataFlow + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { + // Define your source here + none() + } + + predicate isSink(DataFlow::Node node) { + // Define your sink here + none() + } +} + +module Flow = DataFlow::Global; +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "Replace this with your query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/ruby/new-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/ruby/new-query.liquid new file mode 100644 index 0000000..fe06e49 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/ruby/new-query.liquid @@ -0,0 +1,14 @@ +/** + * @id {{language}}/{{query_pack_name}}/{{query_name | downcase}} + * @name {{query_name}} + * @description {{description}} + * @kind problem + * @precision medium + * @problem.severity error + * @tags {{query_pack_name}} + */ + +import {{ql_language_import}} + +from File f +select f, "Replace this with your query." diff --git a/src/CodeQLToolkit.Features/Templates/Query/ruby/qlpack-query.liquid b/src/CodeQLToolkit.Features/Templates/Query/ruby/qlpack-query.liquid new file mode 100644 index 0000000..349cdbf --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/ruby/qlpack-query.liquid @@ -0,0 +1,5 @@ +name: {{query_pack_full_name}} +version: 0.0.0 +library: false +dependencies: + codeql/{{ql_language}}-all: "*" diff --git a/src/CodeQLToolkit.Features/Templates/Query/ruby/qlpack-test.liquid b/src/CodeQLToolkit.Features/Templates/Query/ruby/qlpack-test.liquid new file mode 100644 index 0000000..2845096 --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/ruby/qlpack-test.liquid @@ -0,0 +1,6 @@ +name: {{query_pack_name}} +version: 0.0.0 +dependencies: + codeql/{{ql_language}}-all: "*" + {{query_pack_dependency}}: "*" +extractor: {{ql_language}} diff --git a/src/CodeQLToolkit.Features/Templates/Query/ruby/test.liquid b/src/CodeQLToolkit.Features/Templates/Query/ruby/test.liquid new file mode 100644 index 0000000..b4561fe --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/ruby/test.liquid @@ -0,0 +1,6 @@ +# Test case for {{query_name}} +def main + # ${{query_name}} +end + +main if __FILE__ == $0 diff --git a/src/CodeQLToolkit.Features/Templates/Query/ruby/testref.liquid b/src/CodeQLToolkit.Features/Templates/Query/ruby/testref.liquid new file mode 100644 index 0000000..24b07aa --- /dev/null +++ b/src/CodeQLToolkit.Features/Templates/Query/ruby/testref.liquid @@ -0,0 +1 @@ +{{query_name}}/{{query_name}}.ql diff --git a/src/CodeQLToolkit.Features/Test/Commands/BaseExecuteUnitTestsCommandTarget.cs b/src/CodeQLToolkit.Features/Test/Commands/BaseExecuteUnitTestsCommandTarget.cs index 9b596cb..4617851 100644 --- a/src/CodeQLToolkit.Features/Test/Commands/BaseExecuteUnitTestsCommandTarget.cs +++ b/src/CodeQLToolkit.Features/Test/Commands/BaseExecuteUnitTestsCommandTarget.cs @@ -9,7 +9,7 @@ namespace CodeQLToolkit.Features.Test.Commands { public abstract class BaseExecuteUnitTestsCommandTarget : CommandTarget { - public int NumThreads { get; set; } + public int NumThreads { get; set; } public string WorkDirectory { get; set; } public string RunnerOS { get; set; } public string CLIVersion { get; set; } diff --git a/src/CodeQLToolkit.Features/Test/Commands/BaseGetMatrixCommandTarget.cs b/src/CodeQLToolkit.Features/Test/Commands/BaseGetMatrixCommandTarget.cs index 36d79f6..a309dba 100644 --- a/src/CodeQLToolkit.Features/Test/Commands/BaseGetMatrixCommandTarget.cs +++ b/src/CodeQLToolkit.Features/Test/Commands/BaseGetMatrixCommandTarget.cs @@ -8,6 +8,6 @@ namespace CodeQLToolkit.Features.Test.Commands { public abstract class BaseGetMatrixCommandTarget : CommandTarget { - public string[] OSVersions { get; set; } + public string[] OSVersions { get; set; } } } diff --git a/src/CodeQLToolkit.Features/Test/Commands/Targets/Actions/ExecuteUnitTestsCommandTarget.cs b/src/CodeQLToolkit.Features/Test/Commands/Targets/Actions/ExecuteUnitTestsCommandTarget.cs index a8e4663..60a3f64 100644 --- a/src/CodeQLToolkit.Features/Test/Commands/Targets/Actions/ExecuteUnitTestsCommandTarget.cs +++ b/src/CodeQLToolkit.Features/Test/Commands/Targets/Actions/ExecuteUnitTestsCommandTarget.cs @@ -16,7 +16,7 @@ namespace CodeQLToolkit.Features.Test.Commands.Targets.Actions [AutomationType(AutomationType.ACTIONS)] public class ExecuteUnitTestsCommandTarget : BaseExecuteUnitTestsCommandTarget { - + public override void Run() { Log.G().LogInformation($"Preparing to execute unit tests found in {Base} for Language {Language}..."); @@ -24,10 +24,11 @@ public override void Run() // get a directory to work in var tmpDirectory = WorkDirectory; - var languageRoot = Path.Combine(Base, Language); + var languageRoot = Path.Combine(Base, Language); // check if the language root exists - if (!Directory.Exists(languageRoot)){ + if (!Directory.Exists(languageRoot)) + { DieWithError($"Language root {languageRoot} does not exist so unit tests cannnot be run."); } @@ -37,20 +38,21 @@ public override void Run() Log.G().LogInformation($"Test Directory Inventory {Language}"); Log.G().LogInformation($"-----------------------------------"); - foreach ( string dir in dirs) + foreach (string dir in dirs) { Log.G().LogInformation($"Found test directory: {dir}"); } var transformedDirs = dirs.Select(dir => Path.GetRelativePath(Base, dir)); - if(dirs.Length == 0) + if (dirs.Length == 0) { DieWithError($"No tests detected. Please create unit tests before running this command."); } - + Parallel.For(0, NumThreads, - slice => { + slice => + { TestReport report = new TestReport() { @@ -86,8 +88,8 @@ public override void Run() process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = false; - process.StartInfo.Arguments = $"test run {ExtraCodeQLArgs} --failing-exitcode=122 --slice={slice+1}/{NumThreads} --ram=2048 --format=json --search-path={Language} {testPathString}"; - + process.StartInfo.Arguments = $"test run {ExtraCodeQLArgs} --failing-exitcode=122 --slice={slice + 1}/{NumThreads} --ram=2048 --format=json --search-path={Language} {testPathString}"; + process.Start(); // needed for STDOUT redirection @@ -100,7 +102,7 @@ public override void Run() if (process.ExitCode != 0) { // This fine - if(process.ExitCode == 122) + if (process.ExitCode == 122) { Log.G().LogError($"One more more unit tests failed. Please see the output of the validation step for more information about failed tests cases."); } @@ -108,7 +110,7 @@ public override void Run() else { DieWithError($"Non-test related error while running unit tests. Please check debug output for more infomation."); - } + } } } } diff --git a/src/CodeQLToolkit.Features/Test/Commands/Targets/ValidateUnitTestsCommand.cs b/src/CodeQLToolkit.Features/Test/Commands/Targets/ValidateUnitTestsCommand.cs index 2fc347b..3e71170 100644 --- a/src/CodeQLToolkit.Features/Test/Commands/Targets/ValidateUnitTestsCommand.cs +++ b/src/CodeQLToolkit.Features/Test/Commands/Targets/ValidateUnitTestsCommand.cs @@ -8,7 +8,7 @@ namespace CodeQLToolkit.Features.Test.Commands.Targets { - + public class UnitTestResult { public string test { get; set; } @@ -44,9 +44,9 @@ public override void Run() string json = r.ReadToEnd(); List items = JsonConvert.DeserializeObject>(json); - foreach(var item in items) - { - unitTestResults.Add(item); + foreach (var item in items) + { + unitTestResults.Add(item); } } } @@ -61,7 +61,7 @@ public override void Run() if (item.pass) { - if(PrettyPrint) + if (PrettyPrint) { Console.WriteLine($" ✅ [PASS] ({currentCase} of {totalCases}) {item.test}"); } @@ -127,12 +127,12 @@ public override void Run() } } - var failedTests = unitTestResults.Select(x=>x).Where(x=>x.pass==false).ToArray(); + var failedTests = unitTestResults.Select(x => x).Where(x => x.pass == false).ToArray(); if (failedTests.Length > 0 && !PrettyPrint) { DieWithError("One or more failures during run unit tests."); } - } + } } } diff --git a/src/CodeQLToolkit.Features/Test/Lifecycle/BaseLifecycleTarget.cs b/src/CodeQLToolkit.Features/Test/Lifecycle/BaseLifecycleTarget.cs index 25c0884..8429dbe 100644 --- a/src/CodeQLToolkit.Features/Test/Lifecycle/BaseLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/Test/Lifecycle/BaseLifecycleTarget.cs @@ -8,8 +8,8 @@ namespace CodeQLToolkit.Features.Test.Lifecycle { abstract public class BaseLifecycleTarget : ILifecycleTarget { - public int NumThreads { get; set; } - public string UseRunner { get; set; } + public int NumThreads { get; set; } + public string UseRunner { get; set; } public string ExtraArgs { get; set; } diff --git a/src/CodeQLToolkit.Features/Test/Lifecycle/Models/TestReport.cs b/src/CodeQLToolkit.Features/Test/Lifecycle/Models/TestReport.cs index ffb2635..ba870a0 100644 --- a/src/CodeQLToolkit.Features/Test/Lifecycle/Models/TestReport.cs +++ b/src/CodeQLToolkit.Features/Test/Lifecycle/Models/TestReport.cs @@ -18,9 +18,11 @@ public class TestReport public int NumSlices { get; set; } - public string FileName { - - get { + public string FileName + { + + get + { var savePath = $"test_report_{RunnerOS}_{CLIVersion}_{STDLibIdent}_slice_{Slice}_of_{NumSlices}.json"; @@ -28,6 +30,6 @@ public string FileName { } } - + } } diff --git a/src/CodeQLToolkit.Features/Test/Lifecycle/Targets/Actions/InitLifecycleTarget.cs b/src/CodeQLToolkit.Features/Test/Lifecycle/Targets/Actions/InitLifecycleTarget.cs index be693c4..5f342a0 100644 --- a/src/CodeQLToolkit.Features/Test/Lifecycle/Targets/Actions/InitLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/Test/Lifecycle/Targets/Actions/InitLifecycleTarget.cs @@ -9,9 +9,9 @@ namespace CodeQLToolkit.Features.Test.Lifecycle.Targets.Actions [AutomationType(AutomationType.ACTIONS)] public class InitLifecycleTarget : BaseLifecycleTarget { - public InitLifecycleTarget() + public InitLifecycleTarget() { - AutomationType = AutomationType.ACTIONS; + AutomationType = AutomationType.ACTIONS; } public override void Run() { @@ -37,11 +37,11 @@ public override void Run() language = tmpLanguage, codeqlArgs = EXcodeqlArgs, devMode = DevMode, - branch = Branch + branch = Branch }); - Language = tmpLanguage; + Language = tmpLanguage; var message = @"------------------------------------------ Your repository now has the CodeQL Unit Test Runner installed in `.github/workflows/`. Additionally, diff --git a/src/CodeQLToolkit.Features/Validation/Commands/Targets/CheckQueriesCommandTarget.cs b/src/CodeQLToolkit.Features/Validation/Commands/Targets/CheckQueriesCommandTarget.cs index 0648339..88eaec4 100644 --- a/src/CodeQLToolkit.Features/Validation/Commands/Targets/CheckQueriesCommandTarget.cs +++ b/src/CodeQLToolkit.Features/Validation/Commands/Targets/CheckQueriesCommandTarget.cs @@ -74,7 +74,7 @@ public override void Run() } } - if(shouldFail && !PrettyPrint ) + if (shouldFail && !PrettyPrint) { DieWithError("One or more validation errors found."); } diff --git a/src/CodeQLToolkit.Features/Validation/Lifecycle/Targets/Actions/InitLifecycleTarget.cs b/src/CodeQLToolkit.Features/Validation/Lifecycle/Targets/Actions/InitLifecycleTarget.cs index 1176eec..5bfd1e8 100644 --- a/src/CodeQLToolkit.Features/Validation/Lifecycle/Targets/Actions/InitLifecycleTarget.cs +++ b/src/CodeQLToolkit.Features/Validation/Lifecycle/Targets/Actions/InitLifecycleTarget.cs @@ -21,7 +21,7 @@ public override void Run() // temporarily disable the language resolution var tmpLanguage = Language; Language = null; - + WriteTemplateIfOverwriteOrNotExists("validate-query-metadata", Path.Combine(Base, ".github", "workflows", $"validate-codeql-queries-{tmpLanguage}.yml"), $"Validate CodeQL Queries ({Language})", new { useRunner = UseRunner, diff --git a/src/CodeQLToolkit.Shared/CodeQL/CodeQLInstallation.cs b/src/CodeQLToolkit.Shared/CodeQL/CodeQLInstallation.cs index 9f8ba89..338a8b9 100644 --- a/src/CodeQLToolkit.Shared/CodeQL/CodeQLInstallation.cs +++ b/src/CodeQLToolkit.Shared/CodeQL/CodeQLInstallation.cs @@ -170,12 +170,35 @@ private void PackageInstall() Log.G().LogInformation($"Checkout standard library into.. {StdLibDirectory}"); - var repoPath = Repository.Clone("https://github.com/github/codeql.git", StdLibDirectory); - + // Use direct git command to clone the repository to avoid LibGit2Sharp + // issues with SSH URL rewriting in Git configuration + using (Process gitProcess = new Process()) + { + gitProcess.StartInfo.FileName = "git"; + gitProcess.StartInfo.WorkingDirectory = InstallationDirectory; + gitProcess.StartInfo.UseShellExecute = false; + gitProcess.StartInfo.RedirectStandardOutput = true; + gitProcess.StartInfo.RedirectStandardError = true; + gitProcess.StartInfo.Arguments = $"clone https://github.com/github/codeql.git {Path.GetFileName(StdLibDirectory)}"; + + gitProcess.Start(); + string output = gitProcess.StandardOutput.ReadToEnd(); + string error = gitProcess.StandardError.ReadToEnd(); + gitProcess.WaitForExit(); + + if (gitProcess.ExitCode != 0) + { + Log.G().LogError($"Git clone failed with exit code {gitProcess.ExitCode}"); + Log.G().LogError($"Output: {output}"); + Log.G().LogError($"Error: {error}"); + throw new Exception($"Failed to clone CodeQL standard library repository: {error}"); + } + } Log.G().LogInformation($"Getting standard library version.. {StandardLibraryVersion}"); - using (var repo = new Repository(repoPath)) + // Now use LibGit2Sharp to checkout the specific version since the repo is already cloned + using (var repo = new Repository(StdLibDirectory)) { var tag = repo.Tags[$"refs/tags/{StandardLibraryVersion}"]; diff --git a/src/CodeQLToolkit.Shared/Logging/Log.cs b/src/CodeQLToolkit.Shared/Logging/Log.cs index 9ea52e4..beb6e84 100644 --- a/src/CodeQLToolkit.Shared/Logging/Log.cs +++ b/src/CodeQLToolkit.Shared/Logging/Log.cs @@ -24,7 +24,7 @@ private Log() public static ILogger G() { - return instance._logger; + return instance._logger; } } } diff --git a/src/CodeQLToolkit.Shared/Target/ILifecycleTarget.cs b/src/CodeQLToolkit.Shared/Target/ILifecycleTarget.cs index 31e5902..1be8534 100644 --- a/src/CodeQLToolkit.Shared/Target/ILifecycleTarget.cs +++ b/src/CodeQLToolkit.Shared/Target/ILifecycleTarget.cs @@ -61,7 +61,7 @@ public void WriteTemplateIfOverwriteOrNotExists(string template, string path, st Directory.CreateDirectory(Path.GetDirectoryName(path)); - Log.G().LogInformation($"Writing new {description} in {path}."); + Log.G().LogInformation($"Writing {description} in {path}."); var templatePath = GetTemplatePath(template); @@ -75,19 +75,20 @@ public void WriteTemplateIfOverwriteOrNotExists(string template, string path, st } else { - var t = new TemplateUtil().TemplateFromFile(templatePath); + var templateUtil = new TemplateUtil(); + var t = templateUtil.TemplateFromFile(templatePath); Log.G().LogInformation($"Loaded template {templatePath}"); - var rendered = t.Render(model); + var rendered = templateUtil.RenderTemplateStrictly(t, model); File.WriteAllText(path, rendered); } - + } else { Log.G().LogInformation($"Refusing to overwrite existing {description} in {path}"); } } -} + } } diff --git a/src/CodeQLToolkit.Shared/Target/ScaffoldTarget.cs b/src/CodeQLToolkit.Shared/Target/ScaffoldTarget.cs index 402fcac..901d269 100644 --- a/src/CodeQLToolkit.Shared/Target/ScaffoldTarget.cs +++ b/src/CodeQLToolkit.Shared/Target/ScaffoldTarget.cs @@ -7,13 +7,13 @@ namespace CodeQLToolkit.Shared.Target { public abstract class ScaffoldTarget : ITarget { - + public string Name { get; set; } public LanguageType Language { get; set; } public bool OverwriteExisting { get; set; } public string FeatureName { get; set; } - + public string GetTemplatePathForLanguage(string templateName) { @@ -22,7 +22,7 @@ public string GetTemplatePathForLanguage(string templateName) return Path.Combine("Templates", FeatureName, Language.ToDirectory(), templateName + ".liquid"); } - + public string GetTemplatePath(string templateName) { @@ -33,11 +33,12 @@ public void WriteTemplateIfOverwriteOrNotExists(string template, string path, st { if (!File.Exists(path) || OverwriteExisting) { - Log.G().LogInformation($"Writing new {description} in {path}."); + Log.G().LogInformation($"Writing {description} in {path}."); - var t = new TemplateUtil().TemplateFromFile(GetTemplatePathForLanguage(template)); + var templateUtil = new TemplateUtil(); + var t = templateUtil.TemplateFromFile(GetTemplatePathForLanguage(template)); - var rendered = t.Render(model); + var rendered = templateUtil.RenderTemplateStrictly(t, model); File.WriteAllText(path, rendered); } diff --git a/src/CodeQLToolkit.Shared/Template/TemplateUtil.cs b/src/CodeQLToolkit.Shared/Template/TemplateUtil.cs index 5bc1a58..371d5f5 100644 --- a/src/CodeQLToolkit.Shared/Template/TemplateUtil.cs +++ b/src/CodeQLToolkit.Shared/Template/TemplateUtil.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using Scriban; +using Scriban.Runtime; namespace CodeQLToolkit.Shared.Template { @@ -23,6 +24,33 @@ public Scriban.Template TemplateFromFile(string templateFile) return template; } + public Scriban.Template TemplateFromFileStrict(string templateFile) + { + var templateFilePath = Path.Combine(TemplatePath, templateFile); + + var data = File.ReadAllText(templateFilePath); + + var template = Scriban.Template.ParseLiquid(data); + + return template; + } + + public string RenderTemplateStrictly(Scriban.Template template, object model) + { + // Create a template context with default built-ins and strict variables + var context = new TemplateContext(); + context.StrictVariables = true; + + // Import all the default built-in functions + context.BuiltinObject.Import(new Scriban.Functions.StringFunctions()); + + var scriptObject = new ScriptObject(); + scriptObject.Import(model); + context.PushGlobal(scriptObject); + + return template.Render(context); + } + public string RawTemplateFromFile(string templateFile) { var templateFilePath = Path.Combine(TemplatePath, templateFile); diff --git a/src/CodeQLToolkit.Shared/Types/AutomationFeatureFinder.cs b/src/CodeQLToolkit.Shared/Types/AutomationFeatureFinder.cs index a67d6c5..cdb8285 100644 --- a/src/CodeQLToolkit.Shared/Types/AutomationFeatureFinder.cs +++ b/src/CodeQLToolkit.Shared/Types/AutomationFeatureFinder.cs @@ -16,7 +16,7 @@ public static T FindTargetForAutomationType(AutomationType automationType) var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) - { + { var types = assembly.GetTypes(); foreach (var type in types) @@ -25,7 +25,7 @@ public static T FindTargetForAutomationType(AutomationType automationType) // of targets if (!type.IsSubclassOf(typeof(ITarget)) || !type.IsSubclassOf(typeof(T))) { - continue; + continue; } var attributes = type.GetCustomAttributes(typeof(AutomationTypeAttribute), true); diff --git a/src/CodeQLToolkit.Shared/Types/AutomationType.cs b/src/CodeQLToolkit.Shared/Types/AutomationType.cs index ab4aeb0..ba6231c 100644 --- a/src/CodeQLToolkit.Shared/Types/AutomationType.cs +++ b/src/CodeQLToolkit.Shared/Types/AutomationType.cs @@ -26,12 +26,12 @@ public static AutomationType FromString(this AutomationType automationType, stri public static string ToDirectory(this AutomationType automationType) { - if(automationType == AutomationType.ANY) + if (automationType == AutomationType.ANY) { return "Any"; } - if(automationType == AutomationType.ACTIONS) + if (automationType == AutomationType.ACTIONS) { return "Actions"; } diff --git a/src/CodeQLToolkit.Shared/Utils/FileUtils.cs b/src/CodeQLToolkit.Shared/Utils/FileUtils.cs index e483be2..ceafaeb 100644 --- a/src/CodeQLToolkit.Shared/Utils/FileUtils.cs +++ b/src/CodeQLToolkit.Shared/Utils/FileUtils.cs @@ -31,7 +31,7 @@ public static string SanitizeFilename(string filename) { string f = filename; - foreach(var c in Path.GetInvalidFileNameChars()) + foreach (var c in Path.GetInvalidFileNameChars()) { f = f.Replace(c, '_'); } diff --git a/src/CodeQLToolkit.Shared/Utils/Language.cs b/src/CodeQLToolkit.Shared/Utils/Language.cs index ea17bd2..020ed5d 100644 --- a/src/CodeQLToolkit.Shared/Utils/Language.cs +++ b/src/CodeQLToolkit.Shared/Utils/Language.cs @@ -58,6 +58,11 @@ public static LanguageType FromOptionString(this LanguageType LanguageType, stri return LanguageType.CSHARP; } + if (value.ToLower().Equals("python")) + { + return LanguageType.PYTHON; + } + throw new NotImplementedException(); } @@ -191,30 +196,30 @@ public static string ToExtension(this LanguageType LanguageType) return "c"; } - if(LanguageType == LanguageType.CPP) + if (LanguageType == LanguageType.CPP) { return "cpp"; } - if(LanguageType == LanguageType.JAVASCRIPT) + if (LanguageType == LanguageType.JAVASCRIPT) { return "js"; } - if(LanguageType == LanguageType.GO) + if (LanguageType == LanguageType.GO) { return "go"; } - if(LanguageType == LanguageType.RUBY) + if (LanguageType == LanguageType.RUBY) { return "rb"; } - if(LanguageType == LanguageType.PYTHON) + if (LanguageType == LanguageType.PYTHON) { return "py"; } - if(LanguageType == LanguageType.JAVA) + if (LanguageType == LanguageType.JAVA) { - return "java"; + return "java"; } if (LanguageType == LanguageType.CSHARP) { @@ -225,6 +230,44 @@ public static string ToExtension(this LanguageType LanguageType) throw new NotImplementedException(); } + /// + /// Sanitizes a name to be safe for use as a test directory/module name for the specific language. + /// Different languages have different naming restrictions for modules, packages, and classes. + /// + /// The language type + /// The original name to sanitize + /// A sanitized name safe for the language + public static string ToSafeTestName(this LanguageType languageType, string name) + { + // For most languages that use module/package systems, hyphens are problematic + // and should be replaced with underscores + switch (languageType) + { + case LanguageType.PYTHON: + case LanguageType.RUBY: + case LanguageType.GO: + // These languages have strict module naming rules + return name.Replace("-", "_"); + + case LanguageType.JAVA: + case LanguageType.CSHARP: + // Java and C# package/namespace names can't contain hyphens + return name.Replace("-", "_"); + + case LanguageType.JAVASCRIPT: + // JavaScript module names in some contexts can't contain hyphens + return name.Replace("-", "_"); + + case LanguageType.C: + case LanguageType.CPP: + // C/C++ are more flexible with file names, but underscores are safer + return name.Replace("-", "_"); + + default: + throw new NotImplementedException(); + } + } + } diff --git a/src/CodeQLToolkit.Shared/Utils/PackReader.cs b/src/CodeQLToolkit.Shared/Utils/PackReader.cs index dc4681e..0933367 100644 --- a/src/CodeQLToolkit.Shared/Utils/PackReader.cs +++ b/src/CodeQLToolkit.Shared/Utils/PackReader.cs @@ -26,7 +26,7 @@ public static CodeQLPack read(string path) foreach (var e in root.Children) { - if(e.Key.ToString() == "name") + if (e.Key.ToString() == "name") { pack.Name = e.Value.ToString(); } diff --git a/src/CodeQLToolkit.Shared/Utils/Query.cs b/src/CodeQLToolkit.Shared/Utils/Query.cs index 53f12e4..685a048 100644 --- a/src/CodeQLToolkit.Shared/Utils/Query.cs +++ b/src/CodeQLToolkit.Shared/Utils/Query.cs @@ -37,7 +37,7 @@ public class Query public string QueryTestPackName => $"{QueryPackName}-tests"; - public string GetLanguageImportForLangauge() + public string GetLanguageImportForLanguage() { return Language.ToImport(); } diff --git a/src/CodeQLToolkit.Shared/Utils/StringUtils.cs b/src/CodeQLToolkit.Shared/Utils/StringUtils.cs index d9336fc..f75937a 100644 --- a/src/CodeQLToolkit.Shared/Utils/StringUtils.cs +++ b/src/CodeQLToolkit.Shared/Utils/StringUtils.cs @@ -16,7 +16,7 @@ public static string CreateMD5(string input) byte[] inputBytes = Encoding.ASCII.GetBytes(input); byte[] hashBytes = md5.ComputeHash(inputBytes); - return Convert.ToHexString(hashBytes); + return Convert.ToHexString(hashBytes); } } } diff --git a/test/CodeQLToolkit.Features.Tests/CodeQLToolkit.Features.Tests.csproj b/test/CodeQLToolkit.Features.Tests/CodeQLToolkit.Features.Tests.csproj new file mode 100644 index 0000000..3cca188 --- /dev/null +++ b/test/CodeQLToolkit.Features.Tests/CodeQLToolkit.Features.Tests.csproj @@ -0,0 +1,24 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + + diff --git a/test/CodeQLToolkit.Features.Tests/Query/NewQueryScaffoldTargetTests.cs b/test/CodeQLToolkit.Features.Tests/Query/NewQueryScaffoldTargetTests.cs new file mode 100644 index 0000000..f321233 --- /dev/null +++ b/test/CodeQLToolkit.Features.Tests/Query/NewQueryScaffoldTargetTests.cs @@ -0,0 +1,206 @@ +using CodeQLToolkit.Features.Query.Scaffolding.Targets; +using CodeQLToolkit.Shared.Utils; +using System.IO; + +namespace CodeQLToolkit.Features.Tests.Query +{ + [TestFixture] + public class NewQueryScaffoldTargetTests + { + private string tempDirectory; + private NewQueryScaffoldTarget target; + + [SetUp] + public void Setup() + { + // Create a temporary directory for test outputs + tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + + target = new NewQueryScaffoldTarget + { + Base = tempDirectory, + Language = LanguageType.PYTHON, + QueryPack = "test-queries", + QueryPackScope = "test-scope", + QueryKind = "problem", + CreateTests = false, + CreateQueryPack = false, + OverwriteExisting = true, + FeatureName = "Query" + }; + } + + [TearDown] + public void TearDown() + { + // Clean up temporary directory + if (Directory.Exists(tempDirectory)) + { + Directory.Delete(tempDirectory, true); + } + } + + [Test] + public void QueryNameParameterIsUsedExactlyAsProvided() + { + // Test that custom query names are used exactly as provided without modification + var customQueryName = "MyCustomQueryName"; + target.Name = customQueryName; + + target.Run(); + + // Verify the query file was created with the exact name + var expectedQueryPath = Path.Combine(tempDirectory, "python", "test-queries", "src", customQueryName, $"{customQueryName}.ql"); + Assert.That(File.Exists(expectedQueryPath), Is.True, + $"Query file should be created at {expectedQueryPath}"); + + // Verify the content uses the exact query name + var queryContent = File.ReadAllText(expectedQueryPath); + Assert.That(queryContent, Does.Contain($"@name {customQueryName}"), + "Query content should use the exact query name in @name annotation"); + } + + [Test] + public void QueryNameWithSpecialCharactersIsPreserved() + { + // Test that query names with numbers, underscores, etc. are preserved + var specialQueryName = "Test_Query_123"; + target.Name = specialQueryName; + + target.Run(); + + var expectedQueryPath = Path.Combine(tempDirectory, "python", "test-queries", "src", specialQueryName, $"{specialQueryName}.ql"); + Assert.That(File.Exists(expectedQueryPath), Is.True, + $"Query file should be created with special character name at {expectedQueryPath}"); + + var queryContent = File.ReadAllText(expectedQueryPath); + Assert.That(queryContent, Does.Contain($"@name {specialQueryName}"), + "Query content should preserve special characters in query name"); + } + + [Test] + public void QueryNameMatchingValidationScriptPattern() + { + // Test the exact pattern used by the validation script + var validationQueryName = "TestQueryKindProblem"; + target.Name = validationQueryName; + + target.Run(); + + var expectedQueryPath = Path.Combine(tempDirectory, "python", "test-queries", "src", validationQueryName, $"{validationQueryName}.ql"); + Assert.That(File.Exists(expectedQueryPath), Is.True, + $"Query file should be created with validation script name at {expectedQueryPath}"); + + var queryContent = File.ReadAllText(expectedQueryPath); + Assert.That(queryContent, Does.Contain($"@name {validationQueryName}"), + "Query content should use the exact validation script query name"); + } + + [Test] + public void SimpleQueryNameIsPreserved() + { + // Test that simple query names work correctly + var simpleQueryName = "SimpleQuery"; + target.Name = simpleQueryName; + + target.Run(); + + var expectedQueryPath = Path.Combine(tempDirectory, "python", "test-queries", "src", simpleQueryName, $"{simpleQueryName}.ql"); + Assert.That(File.Exists(expectedQueryPath), Is.True, + $"Query file should be created with simple name at {expectedQueryPath}"); + + var queryContent = File.ReadAllText(expectedQueryPath); + Assert.That(queryContent, Does.Contain($"@name {simpleQueryName}"), + "Query content should use the exact simple query name"); + } + + [Test] + public void QueryNameIsUsedInDirectoryStructure() + { + // Test that the query name is used to create the proper directory structure + var queryName = "DirectoryTestQuery"; + target.Name = queryName; + + target.Run(); + + // Verify directory structure matches the query name + var expectedQueryDir = Path.Combine(tempDirectory, "python", "test-queries", "src", queryName); + Assert.That(Directory.Exists(expectedQueryDir), Is.True, + $"Query directory should be created at {expectedQueryDir}"); + + var expectedQueryFile = Path.Combine(expectedQueryDir, $"{queryName}.ql"); + Assert.That(File.Exists(expectedQueryFile), Is.True, + $"Query file should be created at {expectedQueryFile}"); + } + + [Test] + public void QueryNameInTemplateDataIsUnmodified() + { + // Test that query name is passed to templates without modification + var originalQueryName = "OriginalQueryName123"; + target.Name = originalQueryName; + + target.Run(); + + var expectedQueryPath = Path.Combine(tempDirectory, "python", "test-queries", "src", originalQueryName, $"{originalQueryName}.ql"); + var queryContent = File.ReadAllText(expectedQueryPath); + + // Check both snake_case and camelCase template variables preserve the original name + Assert.That(queryContent, Does.Contain($"@name {originalQueryName}"), + "Template should preserve original query name in @name"); + + // Verify @id uses lowercase version (this is template behavior, not target modification) + Assert.That(queryContent, Does.Contain($"@id python/test-queries/{originalQueryName.ToLower()}"), + "Template should use lowercase version in @id as expected"); + } + + [Test] + [TestCase("TestQuery")] + [TestCase("test_query_name")] + [TestCase("TestQuery123")] + [TestCase("ALLCAPS")] + [TestCase("mixedCASE")] + [TestCase("TestQueryKindProblem")] + public void VariousQueryNamesArePreserved(string queryName) + { + // Test multiple query name formats to ensure all are preserved exactly + target.Name = queryName; + + target.Run(); + + var expectedQueryPath = Path.Combine(tempDirectory, "python", "test-queries", "src", queryName, $"{queryName}.ql"); + Assert.That(File.Exists(expectedQueryPath), Is.True, + $"Query file should be created for name '{queryName}' at {expectedQueryPath}"); + + var queryContent = File.ReadAllText(expectedQueryPath); + Assert.That(queryContent, Does.Contain($"@name {queryName}"), + $"Query content should preserve exact name '{queryName}' in @name annotation"); + } + + [Test] + public void DifferentLanguagesPreserveQueryName() + { + // Test that query name preservation works across different languages + var queryName = "CrossLanguageTest"; + var testLanguages = new[] { LanguageType.PYTHON, LanguageType.JAVA, LanguageType.CSHARP }; + + foreach (var language in testLanguages) + { + // Reset target for each language + target.Language = language; + target.Name = queryName; + + target.Run(); + + var expectedQueryPath = Path.Combine(tempDirectory, language.ToDirectory(), "test-queries", "src", queryName, $"{queryName}.ql"); + Assert.That(File.Exists(expectedQueryPath), Is.True, + $"Query file should be created for {language} at {expectedQueryPath}"); + + var queryContent = File.ReadAllText(expectedQueryPath); + Assert.That(queryContent, Does.Contain($"@name {queryName}"), + $"Query content for {language} should preserve exact name '{queryName}'"); + } + } + } +} diff --git a/test/CodeQLToolkit.Features.Tests/Templates/QueryTemplateTests.cs b/test/CodeQLToolkit.Features.Tests/Templates/QueryTemplateTests.cs new file mode 100644 index 0000000..c7d75f0 --- /dev/null +++ b/test/CodeQLToolkit.Features.Tests/Templates/QueryTemplateTests.cs @@ -0,0 +1,488 @@ +using CodeQLToolkit.Shared.Template; +using CodeQLToolkit.Shared.Utils; +using System.Text.RegularExpressions; + +namespace CodeQLToolkit.Features.Tests.Templates +{ + [TestFixture] + public class QueryTemplateTests + { + private TemplateUtil templateUtil; + private readonly List allSupportedLanguages = new() + { + LanguageType.C, + LanguageType.CPP, + LanguageType.CSHARP, + LanguageType.GO, + LanguageType.JAVA, + LanguageType.JAVASCRIPT, + LanguageType.PYTHON, + LanguageType.RUBY + }; + + [SetUp] + public void Setup() + { + templateUtil = new TemplateUtil(); + } + + [Test] + public void AllLanguagesHaveQueryPackTemplates() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "qlpack-query.liquid"); + + Assert.DoesNotThrow(() => templateUtil.RawTemplateFromFile(templatePath), + $"qlpack-query.liquid template should exist for language {language}"); + } + } + + [Test] + public void AllLanguagesHaveNewQueryTemplates() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "new-query.liquid"); + + Assert.DoesNotThrow(() => templateUtil.RawTemplateFromFile(templatePath), + $"new-query.liquid template should exist for language {language}"); + } + } + + [Test] + public void AllLanguagesHaveTestTemplates() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "test.liquid"); + + Assert.DoesNotThrow(() => templateUtil.RawTemplateFromFile(templatePath), + $"test.liquid template should exist for language {language}"); + } + } + + [Test] + public void QueryPackTemplatesGenerateCorrectDependencies() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "qlpack-query.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + // Test data matching what NewQueryScaffoldTarget passes + var testData = new + { + queryPackScope = "testscope", + queryPackName = "testpack", + ql_language = language.ToDirectory() + }; + + var rendered = template.Render(testData); + + // Check that the dependency is correctly formatted + var expectedDependency = $"codeql/{language.ToDirectory()}-all"; + Assert.That(rendered, Does.Contain(expectedDependency), + $"Template for {language} should generate dependency '{expectedDependency}' but rendered:\n{rendered}"); + + // Ensure no empty variables (like "codeql/-all") + Assert.That(rendered, Does.Not.Match(@"codeql/[^a-z]"), + $"Template for {language} should not contain malformed dependencies with empty language part"); + + // Ensure the template variables are properly substituted + Assert.That(rendered, Does.Not.Contain("{{"), + $"Template for {language} should not contain unresolved template variables"); + Assert.That(rendered, Does.Not.Contain("}}"), + $"Template for {language} should not contain unresolved template variables"); + } + } + + [Test] + public void NewQueryTemplatesGenerateValidQL() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "new-query.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + // Test data matching EXACTLY what NewQueryScaffoldTarget passes + var testData = new + { + language = language.ToDirectory(), + queryPackName = "testpack", + query_pack_name = "testpack", + queryName = "testquery", + query_name = "testquery", + description = "Test query description", + ql_language_import = language.ToImport() + }; + + var rendered = template.Render(testData); + + // Basic validation that it's a valid QL structure + Assert.That(rendered, Does.Contain("import"), + $"Generated query for {language} should contain import statement"); + Assert.That(rendered, Does.Contain("from"), + $"Generated query for {language} should contain 'from' clause"); + Assert.That(rendered, Does.Contain("select"), + $"Generated query for {language} should contain 'select' clause"); + + // Validate that the import statement is correctly rendered + var expectedImport = $"import {language.ToImport()}"; + Assert.That(rendered, Does.Contain(expectedImport), + $"Generated query for {language} should contain correct import statement '{expectedImport}'"); + + // Ensure no unresolved template variables + Assert.That(rendered, Does.Not.Contain("{{"), + $"Template for {language} should not contain unresolved template variables"); + Assert.That(rendered, Does.Not.Contain("}}"), + $"Template for {language} should not contain unresolved template variables"); + } + } + + [Test] + public void LanguageToDirectoryMappingIsConsistent() + { + // Test that Language.ToDirectory() method returns expected values + var expectedMappings = new Dictionary + { + { LanguageType.C, "cpp" }, + { LanguageType.CPP, "cpp" }, + { LanguageType.CSHARP, "csharp" }, + { LanguageType.GO, "go" }, + { LanguageType.JAVA, "java" }, + { LanguageType.JAVASCRIPT, "javascript" }, + { LanguageType.PYTHON, "python" }, + { LanguageType.RUBY, "ruby" } + }; + + foreach (var kvp in expectedMappings) + { + Assert.That(kvp.Key.ToDirectory(), Is.EqualTo(kvp.Value), + $"Language {kvp.Key} should map to directory '{kvp.Value}'"); + } + } + + [Test] + public void AllSupportedLanguagesHaveCompleteTemplateSets() + { + var requiredTemplates = new[] + { + "qlpack-query.liquid", + "new-query.liquid", + "test.liquid", + "expected.liquid", + "testref.liquid", + "qlpack-test.liquid" + }; + + foreach (var language in allSupportedLanguages) + { + foreach (var templateName in requiredTemplates) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), templateName); + + Assert.DoesNotThrow(() => templateUtil.RawTemplateFromFile(templatePath), + $"Template '{templateName}' should exist for language {language}"); + } + } + } + + [Test] + [TestCase(LanguageType.PYTHON, "codeql/python-all")] + [TestCase(LanguageType.JAVA, "codeql/java-all")] + [TestCase(LanguageType.GO, "codeql/go-all")] + [TestCase(LanguageType.RUBY, "codeql/ruby-all")] + [TestCase(LanguageType.CSHARP, "codeql/csharp-all")] + [TestCase(LanguageType.JAVASCRIPT, "codeql/javascript-all")] + [TestCase(LanguageType.CPP, "codeql/cpp-all")] + [TestCase(LanguageType.C, "codeql/cpp-all")] + public void SpecificLanguageDependenciesAreCorrect(LanguageType language, string expectedDependency) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "qlpack-query.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + var testData = new + { + queryPackScope = "testscope", + queryPackName = "testpack", + ql_language = language.ToDirectory() + }; + + var rendered = template.Render(testData); + + Assert.That(rendered, Does.Contain($"{expectedDependency}:"), + $"Template for {language} should generate dependency '{expectedDependency}' but got:\n{rendered}"); + } + + [Test] + public void TestPackTemplatesGenerateCorrectContent() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "qlpack-test.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + // Test data matching what NewQueryScaffoldTarget passes + var testData = new + { + queryPackDependency = $"testscope/testpack", + queryPackScope = "testscope", + queryPackName = $"testpack-tests", // This already includes "-tests" + query_pack_full_name = $"testscope/testpack-tests", + ql_language = language.ToDirectory() + }; + + var rendered = template.Render(testData); + + // Check that the name does not have double "-tests" + Assert.That(rendered, Does.Not.Contain("testpack-tests-tests"), + $"Template for {language} should not have double '-tests' suffix but got:\n{rendered}"); + + // Ensure no unresolved template variables + Assert.That(rendered, Does.Not.Contain("{{"), + $"Template for {language} should not contain unresolved template variables"); + Assert.That(rendered, Does.Not.Contain("}}"), + $"Template for {language} should not contain unresolved template variables"); + + // Should contain the correct dependency + Assert.That(rendered, Does.Contain("testscope/testpack:"), + $"Template for {language} should contain correct dependency reference"); + } + } + + [Test] + public void TestPackTemplatesHandleEmptyScope() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "qlpack-test.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + // Test data with empty scope + var testData = new + { + queryPackDependency = $"testpack", // No scope prefix + queryPackScope = "", + queryPackName = $"testpack-tests", + query_pack_full_name = $"testpack-tests", // No scope prefix + ql_language = language.ToDirectory() + }; + + var rendered = template.Render(testData); + + // Should not contain invalid pack names with leading slash + Assert.That(rendered, Does.Not.Contain("/testpack"), + $"Template for {language} should not contain invalid pack names with leading slash but got:\n{rendered}"); + + // Should contain the correct dependency without scope + Assert.That(rendered, Does.Contain("testpack:"), + $"Template for {language} should contain correct dependency reference without scope"); + + // Ensure no unresolved template variables + Assert.That(rendered, Does.Not.Contain("{{"), + $"Template for {language} should not contain unresolved template variables"); + Assert.That(rendered, Does.Not.Contain("}}"), + $"Template for {language} should not contain unresolved template variables"); + } + } + + [Test] + public void QueryTemplatesGenerateCorrectSyntax() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "new-query.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + // Test data with a mixed-case query name + var testData = new + { + language = language.ToDirectory(), + query_pack_name = "test-queries", + query_name = "TestQuery123", + description = "Test description", + ql_language_import = language.ToImport() + }; + + var rendered = template.Render(testData); + + // Check that @id uses lowercase query name + Assert.That(rendered, Does.Contain($"@id {language.ToDirectory()}/test-queries/testquery123"), + $"Template for {language} should use lowercase query name in @id"); + + // Check that @name preserves original case + Assert.That(rendered, Does.Contain("@name TestQuery123"), + $"Template for {language} should preserve original case in @name"); + + // Check that there are no leading spaces on import, from, select lines + var lines = rendered.Split('\n'); + foreach (var line in lines) + { + if (line.TrimStart().StartsWith("import ") || + line.TrimStart().StartsWith("from ") || + line.TrimStart().StartsWith("select ")) + { + Assert.That(line, Does.Not.StartWith(" "), + $"Template for {language} should not have leading spaces on CodeQL statements. Found: '{line}'"); + } + } + + // Ensure no unresolved template variables + Assert.That(rendered, Does.Not.Contain("{{"), + $"Template for {language} should not contain unresolved template variables"); + Assert.That(rendered, Does.Not.Contain("}}"), + $"Template for {language} should not contain unresolved template variables"); + } + } + + [Test] + public void DataflowQueryTemplatesGenerateValidQL() + { + foreach (var language in allSupportedLanguages) + { + var templatePath = Path.Combine("Templates", "Query", language.ToDirectory(), "new-dataflow-query.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + // Test data matching EXACTLY what NewQueryScaffoldTarget passes for dataflow queries + var testData = new + { + language = language.ToDirectory(), + queryPackName = "testpack", + query_pack_name = "testpack", + queryName = "testquery", + query_name = "testquery", + description = "Test dataflow query description", + ql_language_import = language.ToImport() + }; + + var rendered = template.Render(testData); + + // Validate that it's a valid dataflow QL structure + Assert.That(rendered, Does.Contain("@kind path-problem"), + $"Generated dataflow query for {language} should contain '@kind path-problem'"); + Assert.That(rendered, Does.Contain("import"), + $"Generated dataflow query for {language} should contain import statement"); + + // Validate that the import statement is correctly rendered + var expectedImport = $"import {language.ToImport()}"; + Assert.That(rendered, Does.Contain(expectedImport), + $"Generated dataflow query for {language} should contain correct import statement '{expectedImport}'"); + + // Ensure no unresolved template variables + Assert.That(rendered, Does.Not.Contain("{{"), + $"Dataflow template for {language} should not contain unresolved template variables"); + Assert.That(rendered, Does.Not.Contain("}}"), + $"Dataflow template for {language} should not contain unresolved template variables"); + } + } + + [Test] + public void DetectsMissingTemplateVariables() + { + // This test ensures that missing template variables are detected using strict rendering + var templatePath = Path.Combine("Templates", "Query", "python", "new-query.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + // Pass template data with a MISSING required variable (ql_language_import) + var incompleteTemplateData = new + { + language = "python", + queryPackName = "test-pack", + query_pack_name = "test-pack", + queryName = "TestQuery", + query_name = "TestQuery", + description = "Test description", + // Missing: ql_language_import + }; + + // When rendering with strict variables, this should throw an exception for undefined variables + try + { + var result = templateUtil.RenderTemplateStrictly(template, incompleteTemplateData); + Assert.Fail($"Expected an exception when ql_language_import is missing, but got result: {result}"); + } + catch (Exception ex) + { + // Verify the exception mentions the missing variable + Assert.That(ex.Message, Does.Contain("ql_language_import"), + $"Exception should mention the missing ql_language_import variable. Got: {ex.Message}"); + + // This is expected - the test should pass when an exception is thrown for missing variables + Assert.Pass("Template correctly threw an exception for missing ql_language_import variable"); + } + } + + [Test] + public void StrictRenderingWorksWithCompleteTemplateData() + { + // This test ensures that strict rendering works correctly when all variables are provided + var templatePath = Path.Combine("Templates", "Query", "python", "new-query.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + + // Pass complete template data with ALL required variables + var completeTemplateData = new + { + language = "python", + queryPackName = "test-pack", + query_pack_name = "test-pack", + queryName = "TestQuery", + query_name = "TestQuery", + description = "Test description", + ql_language_import = "python" // This variable is present + }; + + // With all variables provided, strict rendering should work without throwing + string result = null; + Assert.DoesNotThrow(() => + { + result = templateUtil.RenderTemplateStrictly(template, completeTemplateData); + }, "Template should render successfully when all required variables are provided"); + + // Verify the template rendered correctly + Assert.That(result, Is.Not.Null.And.Not.Empty, "Template should produce output"); + Assert.That(result, Does.Contain("import python"), "Template should contain the correct import statement"); + Assert.That(result, Does.Contain("@name TestQuery"), "Template should contain the query name"); + } + + [Test] + public void TemplateDataMatchesNewQueryScaffoldTargetExactly() + { + // This test ensures that NewQueryScaffoldTarget passes the correct template variables + var mockQuery = new CodeQLToolkit.Shared.Utils.Query + { + Language = LanguageType.PYTHON, + QueryPackName = "test-pack", + Name = "TestQuery", + Scope = "test-scope", + Base = "/tmp/test" + }; + + // Use the exact data structure that NewQueryScaffoldTarget should pass + var correctTemplateData = new + { + language = mockQuery.Language.ToDirectory(), + queryPackName = mockQuery.QueryPackName, + query_pack_name = mockQuery.QueryPackName, + queryName = mockQuery.Name, + query_name = mockQuery.Name, + description = "Replace this text with a description of your query.", + ql_language_import = mockQuery.GetLanguageImportForLanguage() // Correct variable name + }; + + var templatePath = Path.Combine("Templates", "Query", "python", "new-query.liquid"); + var template = templateUtil.TemplateFromFile(templatePath); + var rendered = template.Render(correctTemplateData); + + // Verify that the template renders correctly with proper variable names + Assert.That(rendered, Does.Contain("import python"), + "Template should render correct import statement when using proper variable names"); + + // Verify no empty import statements (which would indicate missing variables) + Assert.That(rendered, Does.Not.Contain("import \n"), + "Template should not have empty import statements"); + Assert.That(rendered, Does.Not.Contain("import \r"), + "Template should not have empty import statements"); + } + } +} diff --git a/test/CodeQLToolkit.Features.Tests/Usings.cs b/test/CodeQLToolkit.Features.Tests/Usings.cs new file mode 100644 index 0000000..3244567 --- /dev/null +++ b/test/CodeQLToolkit.Features.Tests/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; diff --git a/test/CodeQLToolkit.Shared.Tests/Utils/FileUtilsTest.cs b/test/CodeQLToolkit.Shared.Tests/Utils/FileUtilsTest.cs index 7646290..9875de6 100644 --- a/test/CodeQLToolkit.Shared.Tests/Utils/FileUtilsTest.cs +++ b/test/CodeQLToolkit.Shared.Tests/Utils/FileUtilsTest.cs @@ -13,11 +13,11 @@ public class FileUtilsTest public void Setup() { } - + [Test] public void TestCreateTempDirectory() { - var dir = FileUtils.CreateTempDirectory(); + var dir = FileUtils.CreateTempDirectory(); Assert.IsTrue(Directory.Exists(dir)); Assert.IsTrue(dir.StartsWith(Path.GetTempPath())); } diff --git a/test/CodeQLToolkit.Shared.Tests/Utils/PackReaderTests.cs b/test/CodeQLToolkit.Shared.Tests/Utils/PackReaderTests.cs index 74868a3..2b18fa3 100644 --- a/test/CodeQLToolkit.Shared.Tests/Utils/PackReaderTests.cs +++ b/test/CodeQLToolkit.Shared.Tests/Utils/PackReaderTests.cs @@ -32,7 +32,7 @@ public void Setup() [Test] public void TestReadPackName() { - Assert.AreEqual("qlt2/stuff2", CodeQLPackReader.read(TestFile).Name); + Assert.That(CodeQLPackReader.read(TestFile).Name, Is.EqualTo("qlt2/stuff2")); } } }