Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-03 21:11:58 +00:00
parent 45eb4bc75f
commit bce07fb4d3
26 changed files with 336 additions and 39 deletions

View File

@ -18,7 +18,7 @@ variables:
# Helm chart ref used by test-on-cng pipeline
GITLAB_HELM_CHART_REF: "be52d36697ab1513512670a5f1456e294d15dbcd"
# Specific ref for cng-mirror project to trigger builds for
GITLAB_CNG_MIRROR_REF: "41602cd52fec706fecb9df273016b2ea6236ee8a"
GITLAB_CNG_MIRROR_REF: "4893053f6121ca32c87d7d28a7f76479cce58a72"
# Makes sure some of the common scripts from pipeline-common use bundler to execute commands
RUN_WITH_BUNDLE: "true"
# Makes sure reporting script defined in .gitlab-qa-report from pipeline-common is executed from correct folder

View File

@ -643,6 +643,9 @@ Cop/ActiveModelErrorsDirectManipulation:
Gitlab/AvoidFeatureGet:
Enabled: true
Gitlab/FeatureFlagKeyDynamic:
Enabled: true
RSpec/WebMockEnable:
Enabled: true
Include:

View File

@ -0,0 +1,32 @@
---
# Cop supports --autocorrect.
Gitlab/FeatureFlagKeyDynamic:
Details: grace period
Exclude:
- 'app/graphql/resolvers/app_config/gitlab_instance_feature_flags_resolver.rb'
- 'app/graphql/resolvers/feature_flag_resolver.rb'
- 'app/services/concerns/measurable.rb'
- 'app/services/service_desk_settings/update_service.rb'
- 'app/workers/concerns/worker_attributes.rb'
- 'app/workers/loose_foreign_keys/cleanup_worker.rb'
- 'ee/app/controllers/remote_development/workspaces_feature_flag_controller.rb'
- 'ee/app/graphql/resolvers/ai/user_available_features_resolver.rb'
- 'ee/app/graphql/resolvers/ai/user_code_suggestions_contexts_resolver.rb'
- 'ee/app/models/concerns/geo/verifiable_replicator.rb'
- 'ee/app/services/search/zoekt/info_service.rb'
- 'ee/lib/gitlab/ai_gateway.rb'
- 'ee/lib/gitlab/geo/replicator.rb'
- 'ee/lib/gitlab/llm/chain/concerns/use_ai_gateway_agent_prompt.rb'
- 'ee/lib/gitlab/llm/completions_factory.rb'
- 'ee/lib/tasks/gitlab/nav/variant_generator.rb'
- 'ee/spec/graphql/resolvers/ai/user_available_features_resolver_spec.rb'
- 'ee/spec/models/gitlab_subscriptions/features_spec.rb'
- 'lib/feature/gitaly.rb'
- 'lib/gitlab/gon_helper.rb'
- 'lib/gitlab/redis/multi_store.rb'
- 'lib/gitlab/sidekiq_middleware/skip_jobs.rb'
- 'lib/gitlab/sidekiq_sharding/router.rb'
- 'lib/web_ide/extension_marketplace.rb'
- 'spec/lib/feature_spec.rb'
- 'spec/requests/api/features_spec.rb'
- 'spec/support_specs/helpers/stub_feature_flags_spec.rb'

View File

@ -13,7 +13,8 @@ import initSourceCodeDropdowns from '~/vue_shared/components/download_dropdown/i
import EmptyProject from '~/pages/projects/show/empty_project';
import initHeaderApp from '~/repository/init_header_app';
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import CompactCodeDropdown from '~/repository/components/code_dropdown/compact_code_dropdown.vue';
import CompactCodeDropdown from 'ee_else_ce/repository/components/code_dropdown/compact_code_dropdown.vue';
import apolloProvider from '~/repository/graphql';
import { initHomePanel } from '../home_panel';
// Project show page loads different overview content based on user preferences
@ -70,7 +71,8 @@ const initCodeDropdown = () => {
if (!codeDropdownEl) return false;
const { sshUrl, httpUrl, kerberosUrl } = codeDropdownEl.dataset;
const { sshUrl, httpUrl, kerberosUrl, newWorkspacePath, projectId, projectPath } =
codeDropdownEl.dataset;
const CodeDropdownComponent =
gon.features.directoryCodeDropdownUpdates && gon.features.blobRepositoryVueHeaderApp
@ -79,12 +81,16 @@ const initCodeDropdown = () => {
return new Vue({
el: codeDropdownEl,
apolloProvider,
render(createElement) {
return createElement(CodeDropdownComponent, {
props: {
sshUrl,
httpUrl,
kerberosUrl,
projectId,
projectPath,
newWorkspacePath,
},
});
},

View File

@ -9,6 +9,7 @@ import CodeDropdownIdeItem from './code_dropdown_ide_item.vue';
import { VSCODE_BASE_URL, JETBRAINS_BASE_URL } from './constants';
export default {
name: 'CECompactCodeDropdown',
components: {
GlDisclosureDropdown,
GlDisclosureDropdownGroup,
@ -241,6 +242,7 @@ export default {
@close-dropdown="closeDropdown"
/>
</gl-disclosure-dropdown-group>
<slot name="gl-ee-compact-code-dropdown"></slot>
</gl-disclosure-dropdown>
</template>
<style>

View File

@ -16,7 +16,6 @@ import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
import BlobControls from '~/repository/components/header_area/blob_controls.vue';
import RepositoryOverflowMenu from '~/repository/components/header_area/repository_overflow_menu.vue';
import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
import CompactCodeDropdown from '~/repository/components/code_dropdown/compact_code_dropdown.vue';
import SourceCodeDownloadDropdown from '~/vue_shared/components/download_dropdown/download_dropdown.vue';
import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_dropdown.vue';
import AddToTree from '~/repository/components/header_area/add_to_tree.vue';
@ -36,7 +35,8 @@ export default {
RepositoryOverflowMenu,
BlobControls,
CodeDropdown,
CompactCodeDropdown,
CompactCodeDropdown: () =>
import('ee_else_ce/repository/components/code_dropdown/compact_code_dropdown.vue'),
SourceCodeDownloadDropdown,
CloneCodeDropdown,
AddToTree,
@ -333,6 +333,8 @@ export default {
:gitpod-url="gitpodUrl"
:current-path="currentPath"
:directory-download-links="downloadLinks"
:project-id="projectId"
:project-path="projectPath"
:show-web-ide-button="showWebIdeButton"
:show-gitpod-button="isGitpodEnabledForInstance"
/>

View File

@ -9,7 +9,7 @@ import createStore from '~/code_navigation/store';
import RefSelector from '~/ref/components/ref_selector.vue';
import HighlightWorker from '~/vue_shared/components/source_viewer/workers/highlight_worker?worker';
import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
import CompactCodeDropdown from '~/repository/components/code_dropdown/compact_code_dropdown.vue';
import CompactCodeDropdown from 'ee_else_ce/repository/components/code_dropdown/compact_code_dropdown.vue';
import App from './components/app.vue';
import Breadcrumbs from './components/header_area/breadcrumbs.vue';
import ForkInfo from './components/fork_info.vue';
@ -185,8 +185,15 @@ export default function setupVueRepositoryList() {
if (!codeDropdownEl) return false;
const { sshUrl, httpUrl, kerberosUrl, xcodeUrl, directoryDownloadLinks } =
codeDropdownEl.dataset;
const {
sshUrl,
httpUrl,
kerberosUrl,
xcodeUrl,
directoryDownloadLinks,
newWorkspacePath,
projectId,
} = codeDropdownEl.dataset;
const CodeDropdownComponent =
gon.features.directoryCodeDropdownUpdates && gon.features.blobRepositoryVueHeaderApp
@ -196,6 +203,7 @@ export default function setupVueRepositoryList() {
return new Vue({
el: codeDropdownEl,
router,
apolloProvider,
render(createElement) {
return createElement(CodeDropdownComponent, {
props: {
@ -205,6 +213,9 @@ export default function setupVueRepositoryList() {
xcodeUrl,
currentPath: this.$route.params.path,
directoryDownloadLinks: JSON.parse(directoryDownloadLinks),
projectId,
projectPath,
newWorkspacePath,
},
});
},

View File

@ -1,5 +1,4 @@
import Vue from 'vue';
import { provideWebIdeLink } from 'ee_else_ce/pages/projects/shared/web_ide_link/provide_web_ide_link';
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import apolloProvider from './graphql';
import projectShortPathQuery from './queries/project_short_path.query.graphql';
@ -64,7 +63,7 @@ export default function initHeaderApp({ router, isReadmeView = false, isBlobView
downloadArtifacts,
projectShortPath,
isBinary,
...options
newWorkspacePath,
} = headerEl.dataset;
const {
@ -126,12 +125,12 @@ export default function initHeaderApp({ router, isReadmeView = false, isBlobView
xcodeUrl,
sshUrl,
kerberosUrl,
newWorkspacePath,
downloadLinks: downloadLinks ? JSON.parse(downloadLinks) : null,
downloadArtifacts: downloadArtifacts ? JSON.parse(downloadArtifacts) : [],
isBlobView,
isBinary: parseBoolean(isBinary),
rootRef,
...provideWebIdeLink(options),
},
apolloProvider,
router: router || createRouter(projectPath, escapedRef),

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module ReadmeHelper
# @return [Hash]
def vue_readme_header_additional_data
{}
end
end
ReadmeHelper.prepend_mod_with("ReadmeHelper")

View File

@ -20,7 +20,7 @@
web_ide_button_options: web_ide_button_data.merge(fork_options).to_json,
web_ide_button_default_branch: @project.default_branch_or_main,
escaped_ref: ActionDispatch::Journey::Router::Utils.escape_path(ref)
} }
}.merge(vue_readme_header_additional_data) }
- else
.nav-block.mt-0

View File

@ -5,4 +5,4 @@ feature_category: geo_replication
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169237
milestone: '17.6'
queued_migration_version: 20241015075957
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250325231842'

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkBackfillDependencyProxyBlobStatesGroupId < Gitlab::Database::Migration[2.2]
milestone '17.11'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillDependencyProxyBlobStatesGroupId',
table_name: :dependency_proxy_blob_states,
column_name: :dependency_proxy_blob_id,
job_arguments: [:group_id, :dependency_proxy_blobs, :group_id, :dependency_proxy_blob_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class SyncRemovePagesDeploymentsDeletedAtIndex < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.11'
INDEX_NAME = 'pages_deployments_deleted_at_index'
COLUMNS = [:id, :project_id, :path_prefix]
def up
remove_concurrent_index_by_name :pages_deployments, name: INDEX_NAME
end
def down
add_concurrent_index :pages_deployments, COLUMNS, where: 'deleted_at IS NULL', name: INDEX_NAME
end
end

View File

@ -0,0 +1 @@
170c287c4509f48acc2e923f15145ec3cdcac85e7bbe71b6737873e33891acd1

View File

@ -0,0 +1 @@
8829b0f5ebc0935cb6ecf1f9e1fa8f1cedab3db3a1565c1fcc008a2e750ab467

View File

@ -37978,8 +37978,6 @@ CREATE INDEX packages_packages_needs_verification ON packages_package_files USIN
CREATE INDEX packages_packages_pending_verification ON packages_package_files USING btree (verified_at NULLS FIRST) WHERE (verification_state = 0);
CREATE INDEX pages_deployments_deleted_at_index ON pages_deployments USING btree (id, project_id, path_prefix) WHERE (deleted_at IS NULL);
CREATE INDEX pages_deployments_deleted_at_null_index ON pages_deployments USING btree (project_id, path_prefix, id) WHERE (deleted_at IS NULL);
CREATE UNIQUE INDEX partial_idx_bulk_import_exports_on_group_user_and_relation ON bulk_import_exports USING btree (group_id, relation, user_id) WHERE ((group_id IS NOT NULL) AND (user_id IS NOT NULL));

View File

@ -12,7 +12,7 @@ For guidelines on specific words, see [the word list](word_list.md).
## The GitLab voice
The GitLab brand guidelines define the
[voice used by the larger organization](https://design.gitlab.com/brand-overview/introduction/#brand-personality).
[voice used by the larger organization](https://design.gitlab.com/brand-messaging/brand-voice).
Building on that guidance, the voice in the GitLab documentation strives to be concise,
direct, and precise. The goal is to provide information that's easy to search and scan.

View File

@ -16159,6 +16159,9 @@ msgstr ""
msgid "Container registry is not enabled on this GitLab instance. Ask an administrator to enable it in order for Auto DevOps to work."
msgstr ""
msgid "Container scanning"
msgstr ""
msgid "ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature."
msgstr ""
@ -20227,9 +20230,6 @@ msgstr ""
msgid "Dependencies|+%{remainingLicensesCount} more"
msgstr ""
msgid "Dependencies|All"
msgstr ""
msgid "Dependencies|Component"
msgstr ""
@ -20350,9 +20350,6 @@ msgstr ""
msgid "Dependencies|Vulnerabilities"
msgstr ""
msgid "Dependencies|Vulnerable components"
msgstr ""
msgid "Dependencies|unknown"
msgstr ""
@ -20365,6 +20362,9 @@ msgstr ""
msgid "Dependency list"
msgstr ""
msgid "Dependency scanning"
msgstr ""
msgid "DependencyListExport|License Identifiers"
msgstr ""
@ -22609,6 +22609,9 @@ msgstr ""
msgid "Dynamic Application Security Testing (DAST)"
msgstr ""
msgid "Dynamic application security testing (DAST)"
msgstr ""
msgid "E-mail:"
msgstr ""
@ -31444,6 +31447,9 @@ msgstr ""
msgid "Infrastructure as Code (IaC) Scanning"
msgstr ""
msgid "Infrastructure as code scanning (IaC)"
msgstr ""
msgid "InfrastructureRegistry|Copy Terraform Command"
msgstr ""
@ -52641,6 +52647,9 @@ msgstr ""
msgid "Secret Detection"
msgstr ""
msgid "Secret detection"
msgstr ""
msgid "Secret push protection"
msgstr ""
@ -57490,6 +57499,9 @@ msgstr ""
msgid "Static Application Security Testing (SAST)"
msgstr ""
msgid "Static application security testing (SAST)"
msgstr ""
msgid "Statistics"
msgstr ""

View File

@ -126,7 +126,7 @@ module QA
end
def select_label(label)
select_labels([label])
select_labels([label.title])
end
def has_label?(label)

View File

@ -377,11 +377,11 @@ module QA
# merge button, in such case we must retry loop otherwise find_element will raise ElementNotFound error
next false unless has_element?('merge-button', wait: 1)
break true unless find_element('merge-button').disabled?
# If the widget shows "Merge blocked: new changes were just added" we can refresh the page and check again
next false if merge_blocked_by_new_changes?
break true unless find_element('merge-button').disabled?
QA::Runtime::Logger.debug("MR widget text: \"#{mr_widget_text}\"")
false

View File

@ -2,10 +2,7 @@
module QA
RSpec.describe 'Create' do
describe 'Merge request rebasing', product_group: :code_review, quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/527224',
type: :investigating
} do
describe 'Merge request rebasing', product_group: :code_review do
let!(:merge_request) { create(:merge_request) }
before do

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Gitlab
# The first argument to `Feature.enabled?` and `Feature.disabled?` should be a literal symbol.
# Dynamic keys are discouraged because they are harder to explicitly search for in the codebase by name.
# Strings are similarly discouraged to simplify exact matching when searching for flag usage.
#
# Feature flags are technical debt (should be short lived), so it is important to ensure we can find all usages in
# order to remove the flag safely.
# More information at https://docs.gitlab.com/development/feature_flags/#feature-flag-definition-and-validation
#
# @example
#
# # bad
# Feature.enabled?('some_flag')
# Feature.enabled?(flag_name)
# Feature.disabled?(flag)
#
# # good
# Feature.enabled?(:some_flag)
# Feature.disabled?(:other_flag)
class FeatureFlagKeyDynamic < RuboCop::Cop::Base
extend AutoCorrector
MSG = 'First argument to `Feature.%<method>s` must be a literal symbol.'
FEATURE_METHODS = %i[enabled? disabled?].freeze
# @!method feature_flag_method?(node)
def_node_matcher :feature_flag_method?, <<~PATTERN
(send
(const nil? :Feature)
${:enabled? :disabled?}
...
)
PATTERN
def on_send(node)
method_name = feature_flag_method?(node)
return unless method_name
first_arg = node.first_argument
return if first_arg&.sym_type?
message = format(MSG, method: method_name)
add_offense(first_arg, message: message) do |corrector|
autocorrect(corrector, first_arg) if first_arg&.str_type?
end
end
alias_method :on_csend, :on_send
private
def autocorrect(corrector, arg_node)
return unless arg_node.str_type?
corrector.replace(arg_node, ":#{arg_node.value}")
end
end
end
end
end

View File

@ -11,7 +11,7 @@ import {
expectedDirectoryDownloadItems,
} from 'jest/repository/components/code_dropdown/mock_data';
describe('Compact Code Dropdown coomponent', () => {
describe('Compact Code Dropdown component', () => {
let wrapper;
const sshUrl = 'ssh://foo.bar';
const httpUrl = 'http://foo.bar';

View File

@ -4,7 +4,6 @@ import RefSelector from '~/ref/components/ref_selector.vue';
import HeaderArea from '~/repository/components/header_area.vue';
import Breadcrumbs from '~/repository/components/header_area/breadcrumbs.vue';
import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
import CompactCodeDropdown from '~/repository/components/code_dropdown/compact_code_dropdown.vue';
import SourceCodeDownloadDropdown from '~/vue_shared/components/download_dropdown/download_dropdown.vue';
import AddToTree from '~/repository/components/header_area/add_to_tree.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
@ -14,6 +13,7 @@ import BlobControls from '~/repository/components/header_area/blob_controls.vue'
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import { headerAppInjected } from 'ee_else_ce_jest/repository/mock_data';
import CompactCodeDropdown from 'ee_else_ce/repository/components/code_dropdown/compact_code_dropdown.vue';
const defaultMockRoute = {
params: {
@ -35,9 +35,9 @@ describe('HeaderArea', () => {
const findFindFileButton = () => wrapper.findByTestId('tree-find-file-control');
const findWebIdeButton = () => wrapper.findByTestId('js-tree-web-ide-link');
const findCodeDropdown = () => wrapper.findComponent(CodeDropdown);
const findCompactCodeDropdown = () => wrapper.findComponent(CompactCodeDropdown);
const findSourceCodeDownloadDropdown = () => wrapper.findComponent(SourceCodeDownloadDropdown);
const findCloneCodeDropdown = () => wrapper.findComponent(CloneCodeDropdown);
const findCompactCodeDropdown = () => wrapper.findComponent(CompactCodeDropdown);
const findAddToTreeDropdown = () => wrapper.findComponent(AddToTree);
const findPageHeading = () => wrapper.findByTestId('repository-heading');
const findFileIcon = () => wrapper.findComponent(FileIcon);
@ -46,7 +46,11 @@ describe('HeaderArea', () => {
const { bindInternalEventDocument } = useMockInternalEventsTracking();
const createComponent = (props = {}, route = { name: 'blobPathDecoded' }, provided = {}) => {
const createComponent = ({
props = {},
route = { name: 'blobPathDecoded' },
provided = {},
} = {}) => {
return shallowMountExtended(HeaderArea, {
provide: {
...headerAppInjected,
@ -62,6 +66,7 @@ describe('HeaderArea', () => {
},
stubs: {
RouterLink: RouterLinkStub,
CompactCodeDropdown,
},
mocks: {
$route: {
@ -94,7 +99,9 @@ describe('HeaderArea', () => {
describe('when rendered for tree view', () => {
beforeEach(() => {
wrapper = createComponent({}, { name: 'treePathDecoded', params: { path: 'project' } });
wrapper = createComponent({
route: { name: 'treePathDecoded', params: { path: 'project' } },
});
});
describe('PageHeading', () => {
@ -168,7 +175,15 @@ describe('HeaderArea', () => {
describe('when rendered for tree view and directory_code_dropdown_updates flag is true', () => {
beforeEach(() => {
wrapper = createComponent({}, {}, { glFeatures: { directoryCodeDropdownUpdates: true } });
wrapper = createComponent({
route: { name: 'treePathDecoded' },
provided: {
glFeatures: {
directoryCodeDropdownUpdates: true,
},
newWorkspacePath: '/workspaces/new',
},
});
});
describe('Add to tree dropdown', () => {
@ -199,7 +214,10 @@ describe('HeaderArea', () => {
});
it('renders RepositoryOverflowMenu component with correct props when on ref different than default branch', () => {
wrapper = createComponent({}, 'treePathDecoded', { comparePath: 'test/project/compare' });
wrapper = createComponent({
route: { name: 'treePathDecoded' },
provided: { comparePath: 'test/project/compare' },
});
expect(findRepositoryOverflowMenu().exists()).toBe(true);
expect(findRepositoryOverflowMenu().props('comparePath')).toBe(
headerAppInjected.comparePath,
@ -210,7 +228,7 @@ describe('HeaderArea', () => {
describe('when rendered for blob view', () => {
it('renders BlobControls component with correct props', () => {
wrapper = createComponent({ refType: 'branch' });
wrapper = createComponent({ props: { refType: 'branch' } });
expect(findBlobControls().exists()).toBe(true);
expect(findBlobControls().props('projectPath')).toBe('test/project');
expect(findBlobControls().props('refType')).toBe('');
@ -235,7 +253,10 @@ describe('HeaderArea', () => {
describe('when rendered for readme project overview', () => {
beforeEach(() => {
wrapper = createComponent({}, { name: 'treePathDecoded' }, { isReadmeView: true });
wrapper = createComponent({
route: { name: 'treePathDecoded' },
provided: { isReadmeView: true },
});
});
it('does not render directory name and icon', () => {
@ -260,7 +281,7 @@ describe('HeaderArea', () => {
describe('when rendered for full project overview', () => {
beforeEach(() => {
wrapper = createComponent({}, { name: 'projectRoot' });
wrapper = createComponent({ route: { name: 'projectRoot' } });
});
it('does not render directory name and icon', () => {

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ReadmeHelper, feature_category: :source_code_management do
it 'returns a hash' do
expect(helper.vue_readme_header_additional_data).to be_a(Hash)
end
end

View File

@ -0,0 +1,90 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/gitlab/feature_flag_key_dynamic'
RSpec.describe RuboCop::Cop::Gitlab::FeatureFlagKeyDynamic, feature_category: :scalability do
context 'when calling Feature.enabled?' do
it 'registers an offense when using a variable as the first argument' do
expect_offense(<<~RUBY)
flag_name = :some_flag
Feature.enabled?(flag_name)
^^^^^^^^^ First argument to `Feature.enabled?` must be a literal symbol.
RUBY
end
it 'registers an offense when using a method call as the first argument' do
expect_offense(<<~RUBY)
Feature.enabled?(get_flag_name)
^^^^^^^^^^^^^ First argument to `Feature.enabled?` must be a literal symbol.
RUBY
end
it 'registers an offense when using a string as the first argument' do
expect_offense(<<~RUBY)
Feature.enabled?('some_flag')
^^^^^^^^^^^ First argument to `Feature.enabled?` must be a literal symbol.
RUBY
expect_correction(<<~RUBY)
Feature.enabled?(:some_flag)
RUBY
end
it 'does not register an offense when using a literal symbol' do
expect_no_offenses(<<~RUBY)
Feature.enabled?(:some_flag)
RUBY
end
it 'does not register an offense when using a literal symbol with additional arguments' do
expect_no_offenses(<<~RUBY)
Feature.enabled?(:some_flag, project)
RUBY
end
end
context 'when calling Feature.disabled?' do
it 'registers an offense when using a variable as the first argument' do
expect_offense(<<~RUBY)
flag_name = :some_flag
Feature.disabled?(flag_name)
^^^^^^^^^ First argument to `Feature.disabled?` must be a literal symbol.
RUBY
end
it 'registers an offense when using a method call as the first argument' do
expect_offense(<<~RUBY)
Feature.disabled?(get_flag_name)
^^^^^^^^^^^^^ First argument to `Feature.disabled?` must be a literal symbol.
RUBY
end
it 'registers an offense when using a string as the first argument' do
expect_offense(<<~RUBY)
Feature.disabled?('some_flag')
^^^^^^^^^^^ First argument to `Feature.disabled?` must be a literal symbol.
RUBY
expect_correction(<<~RUBY)
Feature.disabled?(:some_flag)
RUBY
end
it 'does not register an offense when using a literal symbol' do
expect_no_offenses(<<~RUBY)
Feature.disabled?(:some_flag)
RUBY
end
end
context 'with non-Feature methods' do
it 'does not register an offense for methods on other objects' do
expect_no_offenses(<<~RUBY)
OtherClass.enabled?(flag_name)
something.disabled?(flag_name)
RUBY
end
end
end