diff --git a/doc/user/project/repository/code_suggestions/repository_xray.md b/doc/user/project/repository/code_suggestions/repository_xray.md index 5d5ce745cd286286177869a0ffe0098bf58c35b2..d77dddab18d1053d1bae2e70dd351ec2123c1856 100644 --- a/doc/user/project/repository/code_suggestions/repository_xray.md +++ b/doc/user/project/repository/code_suggestions/repository_xray.md @@ -37,22 +37,22 @@ The Repository X-Ray service is automatically enabled if: The Repository X-Ray searches a maximum of two directory levels from the repository's root. For example, it supports `Gemfile.lock`, `api/Gemfile.lock`, or `api/client/Gemfile.lock`, but not `api/v1/client/Gemfile.lock`. For each language, only the first matching configuration file is processed. Where available, lock files take precedence over their non-lock file counterparts. -| Language | Package manager | Configuration file | GitLab version | -| ---------- |-----------------| -------------------------------- | -------------- | -| C/C++ | Conan | `conanfile.py` | 17.5 or later | -| C/C++ | Conan | `conanfile.txt` | 17.5 or later | -| C/C++ | vcpkg | `vcpkg.json` | 17.5 or later | -| C# | NuGet | `*.csproj` | 17.5 or later | -| Go | Go Modules | `go.mod` | 17.4 or later | -| Java | Gradle | `build.gradle` | 17.4 or later | -| Java | Maven | `pom.xml` | 17.4 or later | -| JavaScript | NPM | `package-lock.json` | 17.5 or later | -| Kotlin | Gradle | `build.gradle.kts` | 17.5 or later | -| PHP | Composer | `composer.lock`, `composer.json` | 17.5 or later | -| Python | Conda | `environment.yml` | 17.5 or later | -| Python | Pip | `requirements.txt` | 17.5 or later | -| Python | Poetry | `poetry.lock`, `pyproject.toml` | 17.5 or later | -| Ruby | RubyGems | `Gemfile.lock` | 17.4 or later | +| Language | Package manager | Configuration file | GitLab version | +| ---------- |-----------------| ------------------------------------ | -------------- | +| C/C++ | Conan | `conanfile.py` | 17.5 or later | +| C/C++ | Conan | `conanfile.txt` | 17.5 or later | +| C/C++ | vcpkg | `vcpkg.json` | 17.5 or later | +| C# | NuGet | `*.csproj` | 17.5 or later | +| Go | Go Modules | `go.mod` | 17.4 or later | +| Java | Gradle | `build.gradle` | 17.4 or later | +| Java | Maven | `pom.xml` | 17.4 or later | +| JavaScript | NPM | `package-lock.json`, `package.json` | 17.5 or later | +| Kotlin | Gradle | `build.gradle.kts` | 17.5 or later | +| PHP | Composer | `composer.lock`, `composer.json` | 17.5 or later | +| Python | Conda | `environment.yml` | 17.5 or later | +| Python | Pip | `requirements.txt` | 17.5 or later | +| Python | Poetry | `poetry.lock`, `pyproject.toml` | 17.5 or later | +| Ruby | RubyGems | `Gemfile.lock` | 17.4 or later | ## Enable Repository X-Ray in your CI pipeline (deprecated) diff --git a/ee/lib/ai/context/dependencies/config_files/constants.rb b/ee/lib/ai/context/dependencies/config_files/constants.rb index 5cb1324c8abf10ac772913a01eb32b22bdf65087..760e549b45df93bc6f17f6fec9fae4ee74bd2977 100644 --- a/ee/lib/ai/context/dependencies/config_files/constants.rb +++ b/ee/lib/ai/context/dependencies/config_files/constants.rb @@ -27,6 +27,7 @@ module Constants ConfigFiles::JavaGradle, ConfigFiles::JavaMaven, ConfigFiles::JavascriptNpmLock, + ConfigFiles::JavascriptNpm, ConfigFiles::KotlinGradle, ConfigFiles::PhpComposerLock, ConfigFiles::PhpComposer, diff --git a/ee/lib/ai/context/dependencies/config_files/javascript_npm.rb b/ee/lib/ai/context/dependencies/config_files/javascript_npm.rb new file mode 100644 index 0000000000000000000000000000000000000000..b06cd5ab2da2ccb24520e03d743b4076ca6a1654 --- /dev/null +++ b/ee/lib/ai/context/dependencies/config_files/javascript_npm.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Ai + module Context + module Dependencies + module ConfigFiles + class JavascriptNpm < Base + def self.file_name_glob + 'package.json' + end + + def self.lang_name + 'JavaScript' + end + + private + + ### Example format: + # "dependencies": { + # "all-the-cities": "3.1.0", + # "argon2": "0.41.1", + # "countly-request": "file:api/utils/countly-request" + # }, + # "devDependencies": { + # "apidoc": "^1.0.1", + # "apidoc-template": "^0.0.2" + # }, + # + def extract_libs + parsed = ::Gitlab::Json.parse(content) + + %w[dependencies devDependencies].flat_map do |key| + dig_in(parsed, key).try(:map) do |name, version| + # skip dependency if the version is a filepath + next if version.include?('/') + + Lib.new(name: name, version: version) + end + end.compact + rescue JSON::ParserError + raise ParsingError, 'content is not valid JSON' + end + end + end + end + end +end diff --git a/ee/spec/lib/ai/context/dependencies/config_files/javascript_npm_spec.rb b/ee/spec/lib/ai/context/dependencies/config_files/javascript_npm_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..67b8569406d055e0083dc78e691f8e8520dfdf65 --- /dev/null +++ b/ee/spec/lib/ai/context/dependencies/config_files/javascript_npm_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ai::Context::Dependencies::ConfigFiles::JavascriptNpm, feature_category: :code_suggestions do + it 'returns the expected language value' do + expect(described_class.lang).to eq('javascript') + end + + it_behaves_like 'parsing a valid dependency config file' do + let(:config_file_content) do + <<~JSON + { + "name": "countly-server", + "version": "24.5.0", + "dependencies": { + "all-the-cities": "3.1.0", + "argon2": "0.41.1", + "countly-request": "file:api/utils/countly-request" + } + } + JSON + end + + let(:expected_formatted_lib_names) do + ['all-the-cities (3.1.0)', 'argon2 (0.41.1)'] + end + end + + context 'when the content contains dev dependencies' do + it_behaves_like 'parsing a valid dependency config file' do + let(:config_file_content) do + <<~JSON + { + "name": "countly-server", + "version": "24.5.0", + "devDependencies": { + "apidoc": "^1.0.1" + }, + "dependencies": { + "all-the-cities": "3.1.0", + "argon2": "0.41.1", + "countly-request": "file:api/utils/countly-request" + } + } + JSON + end + + let(:expected_formatted_lib_names) do + ['apidoc (^1.0.1)', 'all-the-cities (3.1.0)', 'argon2 (0.41.1)'] + end + end + end + + it_behaves_like 'parsing an invalid dependency config file' do + let(:expected_parsing_error_message) { 'content is not valid JSON' } + end + + describe '.matches?' do + using RSpec::Parameterized::TableSyntax + + where(:path, :matches) do + 'package.json' | true + 'dir/package.json' | true + 'dir/subdir/package.json' | true + 'dir/package-lock.json' | false + 'Package.json' | false + 'package_json' | false + end + + with_them do + it 'matches the file name glob pattern at various directory levels' do + expect(described_class.matches?(path)).to eq(matches) + end + end + end +end