From 458b3cb248229b7940221fb1497d965c57c11a6d Mon Sep 17 00:00:00 2001 From: Utkarsh Gupta Date: Thu, 31 Oct 2019 01:37:42 +0530 Subject: [PATCH] New upstream version 12.2.9 --- CHANGELOG.md | 20 ++++ VERSION | 2 +- app/assets/javascripts/project_find_file.js | 3 +- app/controllers/application_controller.rb | 6 +- app/controllers/concerns/internal_redirect.rb | 2 +- app/controllers/concerns/lfs_request.rb | 1 + app/finders/labels_finder.rb | 8 +- app/graphql/gitlab_schema.rb | 10 +- app/helpers/markup_helper.rb | 10 +- app/models/application_setting.rb | 21 +++- .../concerns/mentionable/reference_regexes.rb | 4 +- app/models/discussion.rb | 1 + app/models/member.rb | 1 + app/models/merge_request.rb | 8 ++ app/models/milestone.rb | 4 + app/models/note.rb | 4 + app/models/project.rb | 12 +- app/models/system_note_metadata.rb | 1 + app/models/wiki_page.rb | 6 + app/policies/commit_policy.rb | 1 + app/policies/group_policy.rb | 2 + app/policies/namespace_policy.rb | 2 + app/policies/note_policy.rb | 2 +- app/services/auto_merge/base_service.rb | 7 +- .../merge_requests/assigns_merge_params.rb | 24 ++++ .../error_tracking/list_projects_service.rb | 9 +- app/services/merge_requests/base_service.rb | 14 +++ app/services/merge_requests/build_service.rb | 3 +- app/services/merge_requests/create_service.rb | 1 - app/services/merge_requests/update_service.rb | 4 - app/services/notification_service.rb | 2 +- .../projects/operations/update_service.rb | 6 +- app/services/projects/participants_service.rb | 57 +++++++++- app/services/projects/transfer_service.rb | 2 +- app/validators/addressable_url_validator.rb | 3 +- .../operations/_error_tracking.html.haml | 2 +- core-js/configurator.js | 7 ++ core-js/es/global-this.js | 3 + core-js/es/index.js | 1 + .../async-iterator/as-indexed-pairs.js | 10 ++ core-js/features/async-iterator/drop.js | 10 ++ core-js/features/async-iterator/every.js | 10 ++ core-js/features/async-iterator/filter.js | 10 ++ core-js/features/async-iterator/find.js | 10 ++ core-js/features/async-iterator/flat-map.js | 10 ++ core-js/features/async-iterator/for-each.js | 10 ++ core-js/features/async-iterator/from.js | 10 ++ core-js/features/async-iterator/index.js | 22 ++++ core-js/features/async-iterator/map.js | 10 ++ core-js/features/async-iterator/reduce.js | 10 ++ core-js/features/async-iterator/some.js | 10 ++ core-js/features/async-iterator/take.js | 10 ++ core-js/features/async-iterator/to-array.js | 10 ++ core-js/features/global-this.js | 4 +- core-js/features/iterator/as-indexed-pairs.js | 10 ++ core-js/features/iterator/drop.js | 9 ++ core-js/features/iterator/every.js | 9 ++ core-js/features/iterator/filter.js | 9 ++ core-js/features/iterator/find.js | 9 ++ core-js/features/iterator/flat-map.js | 9 ++ core-js/features/iterator/for-each.js | 9 ++ core-js/features/iterator/from.js | 9 ++ core-js/features/iterator/index.js | 21 ++++ core-js/features/iterator/map.js | 9 ++ core-js/features/iterator/reduce.js | 9 ++ core-js/features/iterator/some.js | 9 ++ core-js/features/iterator/take.js | 9 ++ core-js/features/iterator/to-array.js | 9 ++ core-js/features/map/index.js | 2 + core-js/features/map/update-or-insert.js | 1 + core-js/features/map/upsert.js | 5 + core-js/features/weak-map/index.js | 1 + core-js/features/weak-map/upsert.js | 5 + core-js/internals/add-to-unscopables.js | 4 +- core-js/internals/array-buffer-view-core.js | 4 +- core-js/internals/array-buffer.js | 13 ++- core-js/internals/array-from.js | 5 +- .../array-method-has-species-support.js | 6 +- core-js/internals/async-iterator-iteration.js | 61 ++++++++++ core-js/internals/async-iterator-prototype.js | 37 +++++++ .../internals/create-async-iterator-proxy.js | 61 ++++++++++ core-js/internals/create-iterator-proxy.js | 51 +++++++++ ...e.js => create-non-enumerable-property.js} | 0 core-js/internals/define-iterator.js | 6 +- core-js/internals/export.js | 4 +- .../fix-regexp-well-known-symbol-logic.js | 13 ++- .../internals/get-async-iterator-method.js | 9 ++ core-js/internals/global.js | 9 +- core-js/internals/internal-state.js | 4 +- core-js/internals/iterate.js | 7 +- core-js/internals/iterators-core.js | 6 +- core-js/internals/map-upsert.js | 23 ++++ core-js/internals/microtask.js | 2 +- core-js/internals/native-url.js | 17 ++- core-js/internals/redefine.js | 6 +- core-js/internals/set-global.js | 4 +- core-js/internals/shared-store.js | 7 ++ core-js/internals/shared.js | 8 +- core-js/internals/task.js | 4 +- core-js/internals/to-offset.js | 6 +- core-js/internals/to-positive-integer.js | 7 ++ core-js/internals/typed-array-constructor.js | 17 +-- core-js/internals/typed-array-from.js | 5 +- core-js/internals/v8-version.js | 17 +++ core-js/modules/es.array.concat.js | 6 +- core-js/modules/es.array.reverse.js | 1 + core-js/modules/es.date.to-primitive.js | 6 +- core-js/modules/es.global-this.js | 8 ++ core-js/modules/es.promise.js | 46 ++++---- core-js/modules/es.string.match-all.js | 29 +++-- core-js/modules/es.symbol.js | 6 +- core-js/modules/esnext.aggregate-error.js | 32 +++++- .../esnext.async-iterator.as-indexed-pairs.js | 27 +++++ .../esnext.async-iterator.constructor.js | 29 +++++ core-js/modules/esnext.async-iterator.drop.js | 41 +++++++ .../modules/esnext.async-iterator.every.js | 10 ++ .../modules/esnext.async-iterator.filter.js | 42 +++++++ core-js/modules/esnext.async-iterator.find.js | 10 ++ .../modules/esnext.async-iterator.flat-map.js | 67 +++++++++++ .../modules/esnext.async-iterator.for-each.js | 10 ++ core-js/modules/esnext.async-iterator.from.js | 30 +++++ core-js/modules/esnext.async-iterator.map.js | 30 +++++ .../modules/esnext.async-iterator.reduce.js | 46 ++++++++ core-js/modules/esnext.async-iterator.some.js | 10 ++ core-js/modules/esnext.async-iterator.take.js | 22 ++++ .../modules/esnext.async-iterator.to-array.js | 10 ++ core-js/modules/esnext.global-this.js | 10 +- .../esnext.iterator.as-indexed-pairs.js | 20 ++++ .../modules/esnext.iterator.constructor.js | 43 ++++++++ core-js/modules/esnext.iterator.drop.js | 30 +++++ core-js/modules/esnext.iterator.every.js | 16 +++ core-js/modules/esnext.iterator.filter.js | 30 +++++ core-js/modules/esnext.iterator.find.js | 16 +++ core-js/modules/esnext.iterator.flat-map.js | 45 ++++++++ core-js/modules/esnext.iterator.for-each.js | 11 ++ core-js/modules/esnext.iterator.from.js | 32 ++++++ core-js/modules/esnext.iterator.map.js | 23 ++++ core-js/modules/esnext.iterator.reduce.js | 25 +++++ core-js/modules/esnext.iterator.some.js | 16 +++ core-js/modules/esnext.iterator.take.js | 25 +++++ core-js/modules/esnext.iterator.to-array.js | 15 +++ core-js/modules/esnext.map.reduce.js | 17 +-- .../modules/esnext.map.update-or-insert.js | 17 +-- core-js/modules/esnext.map.upsert.js | 10 ++ core-js/modules/esnext.math.signbit.js | 1 - core-js/modules/esnext.observable.js | 4 +- core-js/modules/esnext.set.reduce.js | 17 +-- core-js/modules/esnext.string.match-all.js | 1 - core-js/modules/esnext.string.replace-all.js | 33 +++--- core-js/modules/esnext.symbol.replace-all.js | 3 +- core-js/modules/esnext.weak-map.upsert.js | 10 ++ .../modules/web.dom-collections.for-each.js | 4 +- .../modules/web.dom-collections.iterator.js | 10 +- core-js/modules/web.url-search-params.js | 46 +++++++- core-js/modules/web.url.js | 1 - core-js/package.json | 6 +- core-js/postinstall.js | 49 +++++++++ core-js/proposals/iterator-helpers.js | 20 ++++ core-js/proposals/map-update-or-insert.js | 3 +- core-js/proposals/map-upsert.js | 5 + core-js/scripts/postinstall.js | 24 ---- core-js/stable/global-this.js | 4 +- core-js/stage/1.js | 1 - core-js/stage/2.js | 4 +- core-js/stage/3.js | 3 +- core-js/stage/4.js | 1 + .../members/share_project_with_groups.md | 4 + .../query_analyzers/recursion_analyzer.rb | 62 +++++++++++ lib/gitlab/other_markup.rb | 2 +- lib/gitlab/search_results.rb | 2 +- lib/gitlab/url_blocker.rb | 44 ++++++-- .../application_controller_spec.rb | 6 - .../concerns/internal_redirect_spec.rb | 3 +- spec/controllers/concerns/lfs_request_spec.rb | 43 +++++++- .../autocomplete_sources_controller_spec.rb | 31 +++--- .../projects/commits_controller_spec.rb | 4 +- .../error_tracking_controller_spec.rb | 2 +- .../projects/issues_controller_spec.rb | 4 +- .../projects/tags_controller_spec.rb | 2 +- spec/controllers/projects_controller_spec.rb | 2 +- .../projects/pipelines/pipelines_spec.rb | 5 +- .../projects/tags/user_views_tags_spec.rb | 2 +- spec/finders/labels_finder_spec.rb | 82 ++++++++++++++ .../graphql/recursive-introspection.graphql | 57 ++++++++++ .../api/graphql/recursive-query.graphql | 47 ++++++++ .../small-recursive-introspection.graphql | 15 +++ spec/helpers/markup_helper_spec.rb | 78 +++++++------ spec/lib/gitlab/url_blocker_spec.rb | 60 ++++++++++ spec/models/application_setting_spec.rb | 6 + spec/models/milestone_spec.rb | 8 ++ spec/models/note_spec.rb | 77 +++++++++++-- spec/models/project_spec.rb | 12 +- spec/models/wiki_page_spec.rb | 17 +++ spec/policies/commit_policy_spec.rb | 48 ++++++-- spec/policies/group_policy_spec.rb | 82 ++++++++++++++ spec/policies/namespace_policy_spec.rb | 3 +- spec/requests/api/commit_statuses_spec.rb | 2 +- .../api/graphql/gitlab_schema_spec.rb | 48 ++++++-- spec/requests/api/merge_requests_spec.rb | 32 ++++++ spec/requests/api/projects_spec.rb | 16 +++ spec/services/auto_merge/base_service_spec.rb | 3 +- ...rge_when_pipeline_succeeds_service_spec.rb | 7 +- .../assigns_merge_params_spec.rb | 70 ++++++++++++ .../list_projects_service_spec.rb | 13 +++ .../merge_requests/build_service_spec.rb | 3 +- .../merge_requests/update_service_spec.rb | 24 ++++ .../operations/update_service_spec.rb | 21 ++++ .../projects/participants_service_spec.rb | 104 ++++++++++++++++++ .../projects/transfer_service_spec.rb | 18 +++ ...ionless_auth_controller_shared_examples.rb | 22 +++- spec/support/helpers/graphql_helpers.rb | 22 ++++ .../controllers/todos_shared_examples.rb | 2 +- .../addressable_url_validator_spec.rb | 63 +++++++++++ 213 files changed, 3085 insertions(+), 372 deletions(-) create mode 100644 app/services/concerns/merge_requests/assigns_merge_params.rb create mode 100644 core-js/es/global-this.js create mode 100644 core-js/features/async-iterator/as-indexed-pairs.js create mode 100644 core-js/features/async-iterator/drop.js create mode 100644 core-js/features/async-iterator/every.js create mode 100644 core-js/features/async-iterator/filter.js create mode 100644 core-js/features/async-iterator/find.js create mode 100644 core-js/features/async-iterator/flat-map.js create mode 100644 core-js/features/async-iterator/for-each.js create mode 100644 core-js/features/async-iterator/from.js create mode 100644 core-js/features/async-iterator/index.js create mode 100644 core-js/features/async-iterator/map.js create mode 100644 core-js/features/async-iterator/reduce.js create mode 100644 core-js/features/async-iterator/some.js create mode 100644 core-js/features/async-iterator/take.js create mode 100644 core-js/features/async-iterator/to-array.js create mode 100644 core-js/features/iterator/as-indexed-pairs.js create mode 100644 core-js/features/iterator/drop.js create mode 100644 core-js/features/iterator/every.js create mode 100644 core-js/features/iterator/filter.js create mode 100644 core-js/features/iterator/find.js create mode 100644 core-js/features/iterator/flat-map.js create mode 100644 core-js/features/iterator/for-each.js create mode 100644 core-js/features/iterator/from.js create mode 100644 core-js/features/iterator/index.js create mode 100644 core-js/features/iterator/map.js create mode 100644 core-js/features/iterator/reduce.js create mode 100644 core-js/features/iterator/some.js create mode 100644 core-js/features/iterator/take.js create mode 100644 core-js/features/iterator/to-array.js create mode 100644 core-js/features/map/upsert.js create mode 100644 core-js/features/weak-map/upsert.js create mode 100644 core-js/internals/async-iterator-iteration.js create mode 100644 core-js/internals/async-iterator-prototype.js create mode 100644 core-js/internals/create-async-iterator-proxy.js create mode 100644 core-js/internals/create-iterator-proxy.js rename core-js/internals/{hide.js => create-non-enumerable-property.js} (100%) create mode 100644 core-js/internals/get-async-iterator-method.js create mode 100644 core-js/internals/map-upsert.js create mode 100644 core-js/internals/shared-store.js create mode 100644 core-js/internals/to-positive-integer.js create mode 100644 core-js/internals/v8-version.js create mode 100644 core-js/modules/es.global-this.js create mode 100644 core-js/modules/esnext.async-iterator.as-indexed-pairs.js create mode 100644 core-js/modules/esnext.async-iterator.constructor.js create mode 100644 core-js/modules/esnext.async-iterator.drop.js create mode 100644 core-js/modules/esnext.async-iterator.every.js create mode 100644 core-js/modules/esnext.async-iterator.filter.js create mode 100644 core-js/modules/esnext.async-iterator.find.js create mode 100644 core-js/modules/esnext.async-iterator.flat-map.js create mode 100644 core-js/modules/esnext.async-iterator.for-each.js create mode 100644 core-js/modules/esnext.async-iterator.from.js create mode 100644 core-js/modules/esnext.async-iterator.map.js create mode 100644 core-js/modules/esnext.async-iterator.reduce.js create mode 100644 core-js/modules/esnext.async-iterator.some.js create mode 100644 core-js/modules/esnext.async-iterator.take.js create mode 100644 core-js/modules/esnext.async-iterator.to-array.js create mode 100644 core-js/modules/esnext.iterator.as-indexed-pairs.js create mode 100644 core-js/modules/esnext.iterator.constructor.js create mode 100644 core-js/modules/esnext.iterator.drop.js create mode 100644 core-js/modules/esnext.iterator.every.js create mode 100644 core-js/modules/esnext.iterator.filter.js create mode 100644 core-js/modules/esnext.iterator.find.js create mode 100644 core-js/modules/esnext.iterator.flat-map.js create mode 100644 core-js/modules/esnext.iterator.for-each.js create mode 100644 core-js/modules/esnext.iterator.from.js create mode 100644 core-js/modules/esnext.iterator.map.js create mode 100644 core-js/modules/esnext.iterator.reduce.js create mode 100644 core-js/modules/esnext.iterator.some.js create mode 100644 core-js/modules/esnext.iterator.take.js create mode 100644 core-js/modules/esnext.iterator.to-array.js create mode 100644 core-js/modules/esnext.map.upsert.js create mode 100644 core-js/modules/esnext.weak-map.upsert.js create mode 100644 core-js/postinstall.js create mode 100644 core-js/proposals/iterator-helpers.js create mode 100644 core-js/proposals/map-upsert.js delete mode 100644 core-js/scripts/postinstall.js create mode 100644 lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb create mode 100644 spec/fixtures/api/graphql/recursive-introspection.graphql create mode 100644 spec/fixtures/api/graphql/recursive-query.graphql create mode 100644 spec/fixtures/api/graphql/small-recursive-introspection.graphql create mode 100644 spec/services/concerns/merge_requests/assigns_merge_params_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index ca765cd771..f5a632ab57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 12.2.9 + +### Security (14 changes) + +- Standardize error response when route is missing. +- Do not display project labels that are not visible for user accessing group labels. +- Show cross-referenced label and milestones in issues' activities only to authorized users. +- Analyze incoming GraphQL queries and check for recursion. +- Disallow unprivileged users from commenting on private repository commits. +- Don't allow maintainers of a target project to delete the source branch of a merge request from a fork. +- Require Maintainer permission on group where project is transferred to. +- Don't leak private members in project member autocomplete suggestions. +- Return 404 on LFS request if project doesn't exist. +- Mask sentry auth token in Error Tracking dashboard. +- Fixes a Open Redirect issue in `InternalRedirect`. +- Sanitize search text to prevent XSS. +- Sanitize all wiki markup formats with GitLab sanitization pipelines. +- Fix stored XSS issue for grafana_url. + + ## 12.2.8 - No changes. diff --git a/VERSION b/VERSION index 415447f476..b328ce6d68 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -12.2.8 +12.2.9 diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 60d3d83a4b..bd5ab4f9ec 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -5,6 +5,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; import axios from '~/lib/utils/axios_utils'; import flash from '~/flash'; import { __ } from '~/locale'; +import sanitize from 'sanitize-html'; // highlight text(awefwbwgtc -> awefwbwgtc ) const highlighter = function(element, text, matches) { @@ -75,7 +76,7 @@ export default class ProjectFindFile { findFile() { var result, searchText; - searchText = this.inputElement.val(); + searchText = sanitize(this.inputElement.val()); result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths; return this.renderList(result, searchText); diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index af6644b8fc..a246ec1553 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,7 +14,7 @@ class ApplicationController < ActionController::Base include SessionlessAuthentication include ConfirmEmailWarning - before_action :authenticate_user! + before_action :authenticate_user!, except: [:route_not_found] before_action :enforce_terms!, if: :should_enforce_terms? before_action :validate_user_service_ticket! before_action :check_password_expiration @@ -92,7 +92,9 @@ class ApplicationController < ActionController::Base if current_user not_found else - authenticate_user! + store_location_for(:user, request.fullpath) unless request.xhr? + + redirect_to new_user_session_path, alert: I18n.t('devise.failure.unauthenticated') end end diff --git a/app/controllers/concerns/internal_redirect.rb b/app/controllers/concerns/internal_redirect.rb index fa3716502a..e314953bb7 100644 --- a/app/controllers/concerns/internal_redirect.rb +++ b/app/controllers/concerns/internal_redirect.rb @@ -6,7 +6,7 @@ module InternalRedirect def safe_redirect_path(path) return unless path # Verify that the string starts with a `/` and a known route character. - return unless path =~ %r{^/[-\w].*$} + return unless path =~ %r{\A/[-\w].*\z} uri = URI(path) # Ignore anything path of the redirect except for the path, querystring and, diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index f7137a0443..d3af15d82f 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -34,6 +34,7 @@ module LfsRequest end def lfs_check_access! + return render_lfs_not_found unless project return if download_request? && lfs_download_access? return if upload_request? && lfs_upload_access? diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index e523942ea4..027cdc4fc7 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -51,7 +51,7 @@ class LabelsFinder < UnionFinder end label_ids << Label.where(group_id: projects.group_ids) - label_ids << Label.where(project_id: projects.select(:id)) unless only_group_labels? + label_ids << Label.where(project_id: ids_user_can_read_labels(projects)) unless only_group_labels? end label_ids @@ -188,4 +188,10 @@ class LabelsFinder < UnionFinder groups.select { |group| authorized_to_read_labels?(group) } end end + + # rubocop: disable CodeReuse/ActiveRecord + def ids_user_can_read_labels(projects) + Project.where(id: projects.select(:id)).ids_with_issuables_available_for(current_user) + end + # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb index 7edd14e48f..c49c4d937c 100644 --- a/app/graphql/gitlab_schema.rb +++ b/app/graphql/gitlab_schema.rb @@ -18,15 +18,15 @@ class GitlabSchema < GraphQL::Schema use Gitlab::Graphql::GenericTracing query_analyzer Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer.new - - query(Types::QueryType) - - default_max_page_size 100 + query_analyzer Gitlab::Graphql::QueryAnalyzers::RecursionAnalyzer.new max_complexity DEFAULT_MAX_COMPLEXITY max_depth DEFAULT_MAX_DEPTH - mutation(Types::MutationType) + query Types::QueryType + mutation Types::MutationType + + default_max_page_size 100 class << self def multiplex(queries, **kwargs) diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index d76a0f3a3b..e2524938e1 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -133,15 +133,7 @@ module MarkupHelper issuable_state_filter_enabled: true ) - html = - case wiki_page.format - when :markdown - markdown_unsafe(text, context) - when :asciidoc - asciidoc_unsafe(text) - else - wiki_page.formatted_content.html_safe - end + html = markup_unsafe(wiki_page.path, text, context) prepare_for_rendering(html, context) end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index d6caf092ed..7b5d67556e 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -7,6 +7,13 @@ class ApplicationSetting < ApplicationRecord include IgnorableColumn include ChronicDurationAttribute + GRAFANA_URL_RULES = { + allow_localhost: true, + allow_local_network: true, + enforce_sanitization: true, + require_absolute: false + }.freeze + add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required } add_authentication_token_field :health_check_access_token @@ -55,6 +62,11 @@ class ApplicationSetting < ApplicationRecord allow_nil: false, qualified_domain_array: true + validates :grafana_url, + allow_blank: true, + allow_nil: true, + addressable_url: GRAFANA_URL_RULES + validates :session_expire_delay, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } @@ -72,7 +84,6 @@ class ApplicationSetting < ApplicationRecord validates :after_sign_out_path, allow_blank: true, addressable_url: true - validates :admin_notification_email, devise_email: true, allow_blank: true @@ -303,6 +314,14 @@ class ApplicationSetting < ApplicationRecord current_without_cache end + def grafana_url + if Gitlab::UrlBlocker.blocked_url?(self[:grafana_url], GRAFANA_URL_RULES) + ApplicationSetting.column_defaults["grafana_url"] + else + self[:grafana_url] + end + end + # By default, the backend is Rails.cache, which uses # ActiveSupport::Cache::RedisStore. Since loading ApplicationSetting # can cause a significant amount of load on Redis, let's cache it in diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb index b8fb3f7192..fb10035b92 100644 --- a/app/models/concerns/mentionable/reference_regexes.rb +++ b/app/models/concerns/mentionable/reference_regexes.rb @@ -13,7 +13,9 @@ module Mentionable def self.other_patterns [ Commit.reference_pattern, - MergeRequest.reference_pattern + MergeRequest.reference_pattern, + Label.reference_pattern, + Milestone.reference_pattern ] end diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 0d066d0d99..b8525f7b13 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -16,6 +16,7 @@ class Discussion :commit_id, :for_commit?, :for_merge_request?, + :noteable_ability_name, :to_ability_name, :editable?, :visible_for?, diff --git a/app/models/member.rb b/app/models/member.rb index dbae107667..2dd3e7b5ce 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -8,6 +8,7 @@ class Member < ApplicationRecord include Gitlab::Access include Presentable include Gitlab::Utils::StrongMemoize + include FromUnion attr_accessor :raw_invite_token diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bfd636fa62..8ef26af84e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -67,6 +67,14 @@ class MergeRequest < ApplicationRecord has_many :merge_request_assignees has_many :assignees, class_name: "User", through: :merge_request_assignees + KNOWN_MERGE_PARAMS = [ + :auto_merge_strategy, + :should_remove_source_branch, + :force_remove_source_branch, + :commit_message, + :squash_commit_message, + :sha + ].freeze serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize after_create :ensure_merge_request_diff diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2ad2838111..012e72ece5 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -254,6 +254,10 @@ class Milestone < ApplicationRecord group || project end + def to_ability_name + model_name.singular + end + def group_milestone? group_id.present? end diff --git a/app/models/note.rb b/app/models/note.rb index 3956ec192b..307f409de0 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -353,6 +353,10 @@ class Note < ApplicationRecord end def to_ability_name + model_name.singular + end + + def noteable_ability_name for_snippet? ? noteable.class.name.underscore : noteable_type.demodulize.underscore end diff --git a/app/models/project.rb b/app/models/project.rb index a1bd5edaba..e2c3ec8c82 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -581,11 +581,11 @@ class Project < ApplicationRecord joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id) end - # Returns ids of projects with milestones available for given user + # Returns ids of projects with issuables available for given user # - # Used on queries to find milestones which user can see - # For example: Milestone.where(project_id: ids_with_milestone_available_for(user)) - def ids_with_milestone_available_for(user) + # Used on queries to find milestones or labels which user can see + # For example: Milestone.where(project_id: ids_with_issuables_available_for(user)) + def ids_with_issuables_available_for(user) with_issues_enabled = with_issues_available_for_user(user).select(:id) with_merge_requests_enabled = with_merge_requests_available_for_user(user).select(:id) @@ -1223,6 +1223,10 @@ class Project < ApplicationRecord end end + def to_ability_name + model_name.singular + end + # rubocop: disable CodeReuse/ServiceClass def execute_hooks(data, hooks_scope = :push_hooks) run_after_commit_or_now do diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb index a19755d286..bf19fd1529 100644 --- a/app/models/system_note_metadata.rb +++ b/app/models/system_note_metadata.rb @@ -10,6 +10,7 @@ class SystemNoteMetadata < ApplicationRecord commit cross_reference close duplicate moved merge + label milestone ].freeze ICON_TYPES = %w[ diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index cd4c789558..1b6d8fc47a 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -138,6 +138,12 @@ class WikiPage @version ||= @page.version end + def path + return unless persisted? + + @path ||= @page.path + end + def versions(options = {}) return [] unless persisted? diff --git a/app/policies/commit_policy.rb b/app/policies/commit_policy.rb index 4d4f0ba926..4b358c45ec 100644 --- a/app/policies/commit_policy.rb +++ b/app/policies/commit_policy.rb @@ -4,4 +4,5 @@ class CommitPolicy < BasePolicy delegate { @subject.project } rule { can?(:download_code) }.enable :read_commit + rule { ~can?(:read_commit) }.prevent :create_note end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index c686e7763b..987f252546 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -124,6 +124,8 @@ class GroupPolicy < BasePolicy rule { developer & developer_maintainer_access }.enable :create_projects rule { create_projects_disabled }.prevent :create_projects + rule { maintainer & can?(:create_projects) }.enable :transfer_projects + def access_level return GroupMember::NO_ACCESS if @user.nil? diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb index 2babcb0a2d..926a8b7264 100644 --- a/app/policies/namespace_policy.rb +++ b/app/policies/namespace_policy.rb @@ -14,4 +14,6 @@ class NamespacePolicy < BasePolicy end rule { personal_project & ~can_create_personal_project }.prevent :create_projects + + rule { (owner | admin) & can?(:create_projects) }.enable :transfer_projects end diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb index b2af6c874c..dcde8cefa0 100644 --- a/app/policies/note_policy.rb +++ b/app/policies/note_policy.rb @@ -9,7 +9,7 @@ class NotePolicy < BasePolicy condition(:editable, scope: :subject) { @subject.editable? } - condition(:can_read_noteable) { can?(:"read_#{@subject.to_ability_name}") } + condition(:can_read_noteable) { can?(:"read_#{@subject.noteable_ability_name}") } condition(:is_visible) { @subject.visible_for?(@user) } diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb index e06659a39c..e08b4ac226 100644 --- a/app/services/auto_merge/base_service.rb +++ b/app/services/auto_merge/base_service.rb @@ -3,12 +3,13 @@ module AutoMerge class BaseService < ::BaseService include Gitlab::Utils::StrongMemoize + include MergeRequests::AssignsMergeParams def execute(merge_request) - merge_request.merge_params.merge!(params) + assign_allowed_merge_params(merge_request, params.merge(auto_merge_strategy: strategy)) + merge_request.auto_merge_enabled = true merge_request.merge_user = current_user - merge_request.auto_merge_strategy = strategy return :failed unless merge_request.save @@ -21,7 +22,7 @@ module AutoMerge end def update(merge_request) - merge_request.merge_params.merge!(params) + assign_allowed_merge_params(merge_request, params.merge(auto_merge_strategy: strategy)) return :failed unless merge_request.save diff --git a/app/services/concerns/merge_requests/assigns_merge_params.rb b/app/services/concerns/merge_requests/assigns_merge_params.rb new file mode 100644 index 0000000000..bd870d9a1e --- /dev/null +++ b/app/services/concerns/merge_requests/assigns_merge_params.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module MergeRequests + module AssignsMergeParams + def self.included(klass) + raise "#{self} can not be included in #{klass} without implementing #current_user" unless klass.method_defined?(:current_user) + end + + def assign_allowed_merge_params(merge_request, merge_params) + known_merge_params = merge_params.to_h.with_indifferent_access.slice(*MergeRequest::KNOWN_MERGE_PARAMS) + + # Not checking `MergeRequest#can_remove_source_branch` as that includes + # other checks that aren't needed here. + known_merge_params.delete(:force_remove_source_branch) unless current_user.can?(:push_code, merge_request.source_project) + + merge_request.merge_params.merge!(known_merge_params) + + # Delete the known params now that they're assigned, so we don't try to + # assign them through an `#assign_attributes` later. + # They could be coming in as strings or symbols + merge_params.to_h.with_indifferent_access.except!(*MergeRequest::KNOWN_MERGE_PARAMS) + end + end +end diff --git a/app/services/error_tracking/list_projects_service.rb b/app/services/error_tracking/list_projects_service.rb index 8d08f0cda9..92d4ef85ec 100644 --- a/app/services/error_tracking/list_projects_service.rb +++ b/app/services/error_tracking/list_projects_service.rb @@ -32,7 +32,7 @@ module ErrorTracking project_slug: 'proj' ) - setting.token = params[:token] + setting.token = token(setting) setting.enabled = true end end @@ -40,5 +40,12 @@ module ErrorTracking def can_read? can?(current_user, :read_sentry_issue, project) end + + def token(setting) + # Use param token if not masked, otherwise use database token + return params[:token] unless /\A\*+\z/.match?(params[:token]) + + setting.token + end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 067510a8a0..b4590c2008 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -2,6 +2,8 @@ module MergeRequests class BaseService < ::IssuableBaseService + include MergeRequests::AssignsMergeParams + def create_note(merge_request, state = merge_request.state) SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, state, nil) end @@ -31,6 +33,18 @@ module MergeRequests private + def create(merge_request) + self.params = assign_allowed_merge_params(merge_request, params) + + super + end + + def update(merge_request) + self.params = assign_allowed_merge_params(merge_request, params) + + super + end + def handle_wip_event(merge_request) if wip_event = params.delete(:wip_event) # We update the title that is provided in the params or we use the mr title diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index b28f80939a..20e60299dd 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -10,13 +10,14 @@ module MergeRequests # TODO: this should handle all quick actions that don't have side effects # https://gitlab.com/gitlab-org/gitlab-ce/issues/53658 merge_quick_actions_into_params!(merge_request, only: [:target_branch]) - merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) if params.has_key?(:force_remove_source_branch) # Assign the projects first so we can use policies for `filter_params` merge_request.author = current_user merge_request.source_project = find_source_project merge_request.target_project = find_target_project + self.params = assign_allowed_merge_params(merge_request, params) + filter_params(merge_request) merge_request.assign_attributes(params.to_h.compact) diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 06e46595b9..bd883a28c7 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -9,7 +9,6 @@ module MergeRequests merge_request.target_project = @project merge_request.source_project = @source_project merge_request.source_branch = params[:source_branch] - merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) create(merge_request) end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index d361e96bab..e890247ffb 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -16,10 +16,6 @@ module MergeRequests params.delete(:force_remove_source_branch) end - if params.has_key?(:force_remove_source_branch) - merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) - end - handle_wip_event(merge_request) update_task_event(merge_request) || update(merge_request) end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 83710ffce2..be213d8ceb 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -281,7 +281,7 @@ class NotificationService end def send_new_note_notifications(note) - notify_method = "note_#{note.to_ability_name}_email".to_sym + notify_method = "note_#{note.noteable_ability_name}_email".to_sym recipients = NotificationRecipientService.build_new_note_recipients(note) recipients.each do |recipient| diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb index 48eddb0e8d..f343e174a6 100644 --- a/app/services/projects/operations/update_service.rb +++ b/app/services/projects/operations/update_service.rb @@ -34,15 +34,17 @@ module Projects organization_slug: settings.dig(:project, :organization_slug) ) - { + params = { error_tracking_setting_attributes: { api_url: api_url, - token: settings[:token], enabled: settings[:enabled], project_name: settings.dig(:project, :name), organization_name: settings.dig(:project, :organization_name) } } + params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value + + params end end end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 7080f388e5..1cd81fe37c 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -7,16 +7,69 @@ module Projects def execute(noteable) @noteable = noteable - participants = noteable_owner + participants_in_noteable + all_members + groups + project_members + participants = + noteable_owner + + participants_in_noteable + + all_members + + groups + + project_members + participants.uniq end def project_members - @project_members ||= sorted(project.team.members) + @project_members ||= sorted(get_project_members) + end + + def get_project_members + members = Member.from_union([project_members_through_ancestral_groups, + project_members_through_invited_groups, + individual_project_members]) + + User.id_in(members.select(:user_id)) end def all_members [{ username: "all", name: "All Project and Group Members", count: project_members.count }] end + + private + + def project_members_through_invited_groups + groups_with_ancestors_ids = Gitlab::ObjectHierarchy + .new(visible_groups) + .base_and_ancestors + .pluck_primary_key + + GroupMember + .active_without_invites_and_requests + .with_source_id(groups_with_ancestors_ids) + end + + def visible_groups + visible_groups = project.invited_groups + + unless project_owner? + visible_groups = visible_groups.public_or_visible_to_user(current_user) + end + + visible_groups + end + + def project_members_through_ancestral_groups + project.group.present? ? project.group.members_with_parents : Member.none + end + + def individual_project_members + project.project_members + end + + def project_owner? + if project.group.present? + project.group.owners.include?(current_user) + else + project.namespace.owner == current_user + end + end end end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 233dcf37e3..b94d618dc4 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -95,7 +95,7 @@ module Projects @new_namespace && can?(current_user, :change_namespace, project) && @new_namespace.id != project.namespace_id && - current_user.can?(:create_projects, @new_namespace) + current_user.can?(:transfer_projects, @new_namespace) end def update_namespace_and_visibility(to_namespace) diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index bb445499ce..1075d71869 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -49,7 +49,8 @@ class AddressableUrlValidator < ActiveModel::EachValidator allow_local_network: true, ascii_only: false, enforce_user: false, - enforce_sanitization: false + enforce_sanitization: false, + require_absolute: true }.freeze DEFAULT_OPTIONS = BLOCKER_VALIDATE_OPTIONS.merge({ diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml index 583fc08f37..589d3037eb 100644 --- a/app/views/projects/settings/operations/_error_tracking.html.haml +++ b/app/views/projects/settings/operations/_error_tracking.html.haml @@ -17,4 +17,4 @@ project: error_tracking_setting_project_json, api_host: setting.api_host, enabled: setting.enabled.to_json, - token: setting.token } } + token: setting.token.present? ? '*' * 12 : nil } } diff --git a/core-js/configurator.js b/core-js/configurator.js index caafac15bb..0a5288eb6f 100644 --- a/core-js/configurator.js +++ b/core-js/configurator.js @@ -1,7 +1,12 @@ +var has = require('./internals/has'); var isArray = require('./internals/is-array'); var isForced = require('./internals/is-forced'); +var shared = require('./internals/shared-store'); + var data = isForced.data; var normalize = isForced.normalize; +var USE_FUNCTION_CONSTRUCTOR = 'USE_FUNCTION_CONSTRUCTOR'; +var ASYNC_ITERATOR_PROTOTYPE = 'AsyncIteratorPrototype'; var setAggressivenessLevel = function (object, constant) { if (isArray(object)) for (var i = 0; i < object.length; i++) data[normalize(object[i])] = constant; @@ -12,5 +17,7 @@ module.exports = function (options) { setAggressivenessLevel(options.useNative, isForced.NATIVE); setAggressivenessLevel(options.usePolyfill, isForced.POLYFILL); setAggressivenessLevel(options.useFeatureDetection, null); + if (has(options, USE_FUNCTION_CONSTRUCTOR)) shared[USE_FUNCTION_CONSTRUCTOR] = !!options[USE_FUNCTION_CONSTRUCTOR]; + if (has(options, ASYNC_ITERATOR_PROTOTYPE)) shared[USE_FUNCTION_CONSTRUCTOR] = options[ASYNC_ITERATOR_PROTOTYPE]; } }; diff --git a/core-js/es/global-this.js b/core-js/es/global-this.js new file mode 100644 index 0000000000..f056723c42 --- /dev/null +++ b/core-js/es/global-this.js @@ -0,0 +1,3 @@ +require('../modules/esnext.global-this'); + +module.exports = require('../internals/global'); diff --git a/core-js/es/index.js b/core-js/es/index.js index dc8344e11f..f9f36ada04 100644 --- a/core-js/es/index.js +++ b/core-js/es/index.js @@ -41,6 +41,7 @@ require('../modules/es.object.lookup-setter'); require('../modules/es.function.bind'); require('../modules/es.function.name'); require('../modules/es.function.has-instance'); +require('../modules/es.global-this'); require('../modules/es.array.from'); require('../modules/es.array.is-array'); require('../modules/es.array.of'); diff --git a/core-js/features/async-iterator/as-indexed-pairs.js b/core-js/features/async-iterator/as-indexed-pairs.js new file mode 100644 index 0000000000..8fd352b6a4 --- /dev/null +++ b/core-js/features/async-iterator/as-indexed-pairs.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.as-indexed-pairs'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'asIndexedPairs'); diff --git a/core-js/features/async-iterator/drop.js b/core-js/features/async-iterator/drop.js new file mode 100644 index 0000000000..ca33f5b7c0 --- /dev/null +++ b/core-js/features/async-iterator/drop.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.drop'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'drop'); diff --git a/core-js/features/async-iterator/every.js b/core-js/features/async-iterator/every.js new file mode 100644 index 0000000000..b090f1d222 --- /dev/null +++ b/core-js/features/async-iterator/every.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.every'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'every'); diff --git a/core-js/features/async-iterator/filter.js b/core-js/features/async-iterator/filter.js new file mode 100644 index 0000000000..4a37bd0edc --- /dev/null +++ b/core-js/features/async-iterator/filter.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.filter'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'filter'); diff --git a/core-js/features/async-iterator/find.js b/core-js/features/async-iterator/find.js new file mode 100644 index 0000000000..6824ee9fb7 --- /dev/null +++ b/core-js/features/async-iterator/find.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.find'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'find'); diff --git a/core-js/features/async-iterator/flat-map.js b/core-js/features/async-iterator/flat-map.js new file mode 100644 index 0000000000..ef26e56e04 --- /dev/null +++ b/core-js/features/async-iterator/flat-map.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.flat-map'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'flatMap'); diff --git a/core-js/features/async-iterator/for-each.js b/core-js/features/async-iterator/for-each.js new file mode 100644 index 0000000000..ff0a34a3ea --- /dev/null +++ b/core-js/features/async-iterator/for-each.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.for-each'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'forEach'); diff --git a/core-js/features/async-iterator/from.js b/core-js/features/async-iterator/from.js new file mode 100644 index 0000000000..97275d91b4 --- /dev/null +++ b/core-js/features/async-iterator/from.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.from'); +require('../../modules/web.dom-collections.iterator'); + +var path = require('../../internals/path'); + +module.exports = path.AsyncIterator.from; diff --git a/core-js/features/async-iterator/index.js b/core-js/features/async-iterator/index.js new file mode 100644 index 0000000000..2f188c63b3 --- /dev/null +++ b/core-js/features/async-iterator/index.js @@ -0,0 +1,22 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.as-indexed-pairs'); +require('../../modules/esnext.async-iterator.drop'); +require('../../modules/esnext.async-iterator.every'); +require('../../modules/esnext.async-iterator.filter'); +require('../../modules/esnext.async-iterator.find'); +require('../../modules/esnext.async-iterator.flat-map'); +require('../../modules/esnext.async-iterator.for-each'); +require('../../modules/esnext.async-iterator.from'); +require('../../modules/esnext.async-iterator.map'); +require('../../modules/esnext.async-iterator.reduce'); +require('../../modules/esnext.async-iterator.some'); +require('../../modules/esnext.async-iterator.take'); +require('../../modules/esnext.async-iterator.to-array'); +require('../../modules/web.dom-collections.iterator'); + +var path = require('../../internals/path'); + +module.exports = path.AsyncIterator; diff --git a/core-js/features/async-iterator/map.js b/core-js/features/async-iterator/map.js new file mode 100644 index 0000000000..a07254f6d6 --- /dev/null +++ b/core-js/features/async-iterator/map.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.map'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'map'); diff --git a/core-js/features/async-iterator/reduce.js b/core-js/features/async-iterator/reduce.js new file mode 100644 index 0000000000..b05022d10c --- /dev/null +++ b/core-js/features/async-iterator/reduce.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.reduce'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'reduce'); diff --git a/core-js/features/async-iterator/some.js b/core-js/features/async-iterator/some.js new file mode 100644 index 0000000000..1d08566f66 --- /dev/null +++ b/core-js/features/async-iterator/some.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.some'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'some'); diff --git a/core-js/features/async-iterator/take.js b/core-js/features/async-iterator/take.js new file mode 100644 index 0000000000..c67587aff7 --- /dev/null +++ b/core-js/features/async-iterator/take.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.take'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'take'); diff --git a/core-js/features/async-iterator/to-array.js b/core-js/features/async-iterator/to-array.js new file mode 100644 index 0000000000..30f80b8d67 --- /dev/null +++ b/core-js/features/async-iterator/to-array.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.promise'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.async-iterator.constructor'); +require('../../modules/esnext.async-iterator.to-array'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('AsyncIterator', 'toArray'); diff --git a/core-js/features/global-this.js b/core-js/features/global-this.js index f056723c42..bd15daee57 100644 --- a/core-js/features/global-this.js +++ b/core-js/features/global-this.js @@ -1,3 +1 @@ -require('../modules/esnext.global-this'); - -module.exports = require('../internals/global'); +module.exports = require('../es/global-this'); diff --git a/core-js/features/iterator/as-indexed-pairs.js b/core-js/features/iterator/as-indexed-pairs.js new file mode 100644 index 0000000000..fdf2d31da4 --- /dev/null +++ b/core-js/features/iterator/as-indexed-pairs.js @@ -0,0 +1,10 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.as-indexed-pairs'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'asIndexedPairs'); + diff --git a/core-js/features/iterator/drop.js b/core-js/features/iterator/drop.js new file mode 100644 index 0000000000..52b424561b --- /dev/null +++ b/core-js/features/iterator/drop.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.drop'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'drop'); diff --git a/core-js/features/iterator/every.js b/core-js/features/iterator/every.js new file mode 100644 index 0000000000..cc3fbedc34 --- /dev/null +++ b/core-js/features/iterator/every.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.every'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'every'); diff --git a/core-js/features/iterator/filter.js b/core-js/features/iterator/filter.js new file mode 100644 index 0000000000..f744150c3b --- /dev/null +++ b/core-js/features/iterator/filter.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.filter'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'filter'); diff --git a/core-js/features/iterator/find.js b/core-js/features/iterator/find.js new file mode 100644 index 0000000000..dfed16c520 --- /dev/null +++ b/core-js/features/iterator/find.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.find'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'find'); diff --git a/core-js/features/iterator/flat-map.js b/core-js/features/iterator/flat-map.js new file mode 100644 index 0000000000..81e309842a --- /dev/null +++ b/core-js/features/iterator/flat-map.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.flat-map'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'flatMap'); diff --git a/core-js/features/iterator/for-each.js b/core-js/features/iterator/for-each.js new file mode 100644 index 0000000000..e7110344bf --- /dev/null +++ b/core-js/features/iterator/for-each.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.for-each'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'forEach'); diff --git a/core-js/features/iterator/from.js b/core-js/features/iterator/from.js new file mode 100644 index 0000000000..e79d1a103f --- /dev/null +++ b/core-js/features/iterator/from.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.from'); +require('../../modules/web.dom-collections.iterator'); + +var path = require('../../internals/path'); + +module.exports = path.Iterator.from; diff --git a/core-js/features/iterator/index.js b/core-js/features/iterator/index.js new file mode 100644 index 0000000000..6e86af7659 --- /dev/null +++ b/core-js/features/iterator/index.js @@ -0,0 +1,21 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.as-indexed-pairs'); +require('../../modules/esnext.iterator.drop'); +require('../../modules/esnext.iterator.every'); +require('../../modules/esnext.iterator.filter'); +require('../../modules/esnext.iterator.find'); +require('../../modules/esnext.iterator.flat-map'); +require('../../modules/esnext.iterator.for-each'); +require('../../modules/esnext.iterator.from'); +require('../../modules/esnext.iterator.map'); +require('../../modules/esnext.iterator.reduce'); +require('../../modules/esnext.iterator.some'); +require('../../modules/esnext.iterator.take'); +require('../../modules/esnext.iterator.to-array'); +require('../../modules/web.dom-collections.iterator'); + +var path = require('../../internals/path'); + +module.exports = path.Iterator; diff --git a/core-js/features/iterator/map.js b/core-js/features/iterator/map.js new file mode 100644 index 0000000000..060d0f5d42 --- /dev/null +++ b/core-js/features/iterator/map.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.map'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'map'); diff --git a/core-js/features/iterator/reduce.js b/core-js/features/iterator/reduce.js new file mode 100644 index 0000000000..7049b475ab --- /dev/null +++ b/core-js/features/iterator/reduce.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.reduce'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'reduce'); diff --git a/core-js/features/iterator/some.js b/core-js/features/iterator/some.js new file mode 100644 index 0000000000..0a0a20c78c --- /dev/null +++ b/core-js/features/iterator/some.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.some'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'some'); diff --git a/core-js/features/iterator/take.js b/core-js/features/iterator/take.js new file mode 100644 index 0000000000..962c4ad960 --- /dev/null +++ b/core-js/features/iterator/take.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.take'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'take'); diff --git a/core-js/features/iterator/to-array.js b/core-js/features/iterator/to-array.js new file mode 100644 index 0000000000..42f490ba0c --- /dev/null +++ b/core-js/features/iterator/to-array.js @@ -0,0 +1,9 @@ +require('../../modules/es.object.to-string'); +require('../../modules/es.string.iterator'); +require('../../modules/esnext.iterator.constructor'); +require('../../modules/esnext.iterator.to-array'); +require('../../modules/web.dom-collections.iterator'); + +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Iterator', 'toArray'); diff --git a/core-js/features/map/index.js b/core-js/features/map/index.js index bc7a5360c0..4a9b28ce1a 100644 --- a/core-js/features/map/index.js +++ b/core-js/features/map/index.js @@ -17,4 +17,6 @@ require('../../modules/esnext.map.merge'); require('../../modules/esnext.map.reduce'); require('../../modules/esnext.map.some'); require('../../modules/esnext.map.update'); +// TODO: remove from `core-js@4` require('../../modules/esnext.map.update-or-insert'); +require('../../modules/esnext.map.upsert'); diff --git a/core-js/features/map/update-or-insert.js b/core-js/features/map/update-or-insert.js index 09c43531c4..f195f576c5 100644 --- a/core-js/features/map/update-or-insert.js +++ b/core-js/features/map/update-or-insert.js @@ -1,3 +1,4 @@ +// TODO: remove from `core-js@4` require('../../modules/es.map'); require('../../modules/esnext.map.update-or-insert'); var entryUnbind = require('../../internals/entry-unbind'); diff --git a/core-js/features/map/upsert.js b/core-js/features/map/upsert.js new file mode 100644 index 0000000000..e658a40dea --- /dev/null +++ b/core-js/features/map/upsert.js @@ -0,0 +1,5 @@ +require('../../modules/es.map'); +require('../../modules/esnext.map.upsert'); +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('Map', 'upsert'); diff --git a/core-js/features/weak-map/index.js b/core-js/features/weak-map/index.js index a4f4bae85a..9a3ffd4ab4 100644 --- a/core-js/features/weak-map/index.js +++ b/core-js/features/weak-map/index.js @@ -3,3 +3,4 @@ module.exports = require('../../es/weak-map'); require('../../modules/esnext.weak-map.from'); require('../../modules/esnext.weak-map.of'); require('../../modules/esnext.weak-map.delete-all'); +require('../../modules/esnext.weak-map.upsert'); diff --git a/core-js/features/weak-map/upsert.js b/core-js/features/weak-map/upsert.js new file mode 100644 index 0000000000..509f9edc8a --- /dev/null +++ b/core-js/features/weak-map/upsert.js @@ -0,0 +1,5 @@ +require('../../modules/es.weak-map'); +require('../../modules/esnext.weak-map.upsert'); +var entryUnbind = require('../../internals/entry-unbind'); + +module.exports = entryUnbind('WeakMap', 'upsert'); diff --git a/core-js/internals/add-to-unscopables.js b/core-js/internals/add-to-unscopables.js index 54043cf7b8..14de356c5b 100644 --- a/core-js/internals/add-to-unscopables.js +++ b/core-js/internals/add-to-unscopables.js @@ -1,6 +1,6 @@ var wellKnownSymbol = require('../internals/well-known-symbol'); var create = require('../internals/object-create'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var UNSCOPABLES = wellKnownSymbol('unscopables'); var ArrayPrototype = Array.prototype; @@ -8,7 +8,7 @@ var ArrayPrototype = Array.prototype; // Array.prototype[@@unscopables] // https://tc39.github.io/ecma262/#sec-array.prototype-@@unscopables if (ArrayPrototype[UNSCOPABLES] == undefined) { - hide(ArrayPrototype, UNSCOPABLES, create(null)); + createNonEnumerableProperty(ArrayPrototype, UNSCOPABLES, create(null)); } // add a key to Array.prototype[@@unscopables] diff --git a/core-js/internals/array-buffer-view-core.js b/core-js/internals/array-buffer-view-core.js index 30934d6073..8ab1cea7fb 100644 --- a/core-js/internals/array-buffer-view-core.js +++ b/core-js/internals/array-buffer-view-core.js @@ -4,7 +4,7 @@ var global = require('../internals/global'); var isObject = require('../internals/is-object'); var has = require('../internals/has'); var classof = require('../internals/classof'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var redefine = require('../internals/redefine'); var defineProperty = require('../internals/object-define-property').f; var getPrototypeOf = require('../internals/object-get-prototype-of'); @@ -140,7 +140,7 @@ if (DESCRIPTORS && !has(TypedArrayPrototype, TO_STRING_TAG)) { return isObject(this) ? this[TYPED_ARRAY_TAG] : undefined; } }); for (NAME in TypedArrayConstructorsList) if (global[NAME]) { - hide(global[NAME], TYPED_ARRAY_TAG, NAME); + createNonEnumerableProperty(global[NAME], TYPED_ARRAY_TAG, NAME); } } diff --git a/core-js/internals/array-buffer.js b/core-js/internals/array-buffer.js index 731c55addf..560d530438 100644 --- a/core-js/internals/array-buffer.js +++ b/core-js/internals/array-buffer.js @@ -2,7 +2,7 @@ var global = require('../internals/global'); var DESCRIPTORS = require('../internals/descriptors'); var NATIVE_ARRAY_BUFFER = require('../internals/array-buffer-view-core').NATIVE_ARRAY_BUFFER; -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var redefineAll = require('../internals/redefine-all'); var fails = require('../internals/fails'); var anInstance = require('../internals/an-instance'); @@ -268,7 +268,9 @@ if (!NATIVE_ARRAY_BUFFER) { }; var ArrayBufferPrototype = $ArrayBuffer[PROTOTYPE] = NativeArrayBuffer[PROTOTYPE]; for (var keys = getOwnPropertyNames(NativeArrayBuffer), j = 0, key; keys.length > j;) { - if (!((key = keys[j++]) in $ArrayBuffer)) hide($ArrayBuffer, key, NativeArrayBuffer[key]); + if (!((key = keys[j++]) in $ArrayBuffer)) { + createNonEnumerableProperty($ArrayBuffer, key, NativeArrayBuffer[key]); + } } ArrayBufferPrototype.constructor = $ArrayBuffer; } @@ -289,5 +291,8 @@ if (!NATIVE_ARRAY_BUFFER) { setToStringTag($ArrayBuffer, ARRAY_BUFFER); setToStringTag($DataView, DATA_VIEW); -exports[ARRAY_BUFFER] = $ArrayBuffer; -exports[DATA_VIEW] = $DataView; + +module.exports = { + ArrayBuffer: $ArrayBuffer, + DataView: $DataView +}; diff --git a/core-js/internals/array-from.js b/core-js/internals/array-from.js index 9a689463a0..e45ebe493c 100644 --- a/core-js/internals/array-from.js +++ b/core-js/internals/array-from.js @@ -17,13 +17,14 @@ module.exports = function from(arrayLike /* , mapfn = undefined, thisArg = undef var mapping = mapfn !== undefined; var index = 0; var iteratorMethod = getIteratorMethod(O); - var length, result, step, iterator; + var length, result, step, iterator, next; if (mapping) mapfn = bind(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2); // if the target is not iterable or it's an array with the default iterator - use a simple case if (iteratorMethod != undefined && !(C == Array && isArrayIteratorMethod(iteratorMethod))) { iterator = iteratorMethod.call(O); + next = iterator.next; result = new C(); - for (;!(step = iterator.next()).done; index++) { + for (;!(step = next.call(iterator)).done; index++) { createProperty(result, index, mapping ? callWithSafeIterationClosing(iterator, mapfn, [step.value, index], true) : step.value diff --git a/core-js/internals/array-method-has-species-support.js b/core-js/internals/array-method-has-species-support.js index c93ed5c487..55396dab07 100644 --- a/core-js/internals/array-method-has-species-support.js +++ b/core-js/internals/array-method-has-species-support.js @@ -1,10 +1,14 @@ var fails = require('../internals/fails'); var wellKnownSymbol = require('../internals/well-known-symbol'); +var V8_VERSION = require('../internals/v8-version'); var SPECIES = wellKnownSymbol('species'); module.exports = function (METHOD_NAME) { - return !fails(function () { + // We can't use this feature detection in V8 since it causes + // deoptimization and serious performance degradation + // https://github.com/zloirock/core-js/issues/677 + return V8_VERSION >= 51 || !fails(function () { var array = []; var constructor = array.constructor = {}; constructor[SPECIES] = function () { diff --git a/core-js/internals/async-iterator-iteration.js b/core-js/internals/async-iterator-iteration.js new file mode 100644 index 0000000000..94d25608ab --- /dev/null +++ b/core-js/internals/async-iterator-iteration.js @@ -0,0 +1,61 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var getBuiltIn = require('../internals/get-built-in'); + +var Promise = getBuiltIn('Promise'); +var push = [].push; + +var createMethod = function (TYPE) { + var IS_TO_ARRAY = TYPE == 0; + var IS_FOR_EACH = TYPE == 1; + var IS_EVERY = TYPE == 2; + var IS_SOME = TYPE == 3; + return function (iterator, fn) { + anObject(iterator); + var next = aFunction(iterator.next); + var array = IS_TO_ARRAY ? [] : undefined; + if (!IS_TO_ARRAY) aFunction(fn); + + return new Promise(function (resolve, reject) { + var loop = function () { + try { + Promise.resolve(anObject(next.call(iterator))).then(function (step) { + try { + if (anObject(step).done) { + resolve(IS_TO_ARRAY ? array : IS_SOME ? false : IS_EVERY || undefined); + } else { + var value = step.value; + if (IS_TO_ARRAY) { + push.call(array, value); + loop(); + } else { + Promise.resolve(fn(value)).then(function (result) { + if (IS_FOR_EACH) { + loop(); + } else if (IS_EVERY) { + result ? loop() : resolve(false); + } else { + result ? resolve(IS_SOME || value) : loop(); + } + }, reject); + } + } + } catch (err) { reject(err); } + }, reject); + } catch (error) { reject(error); } + }; + + loop(); + }); + }; +}; + +module.exports = { + toArray: createMethod(0), + forEach: createMethod(1), + every: createMethod(2), + some: createMethod(3), + find: createMethod(4) +}; diff --git a/core-js/internals/async-iterator-prototype.js b/core-js/internals/async-iterator-prototype.js new file mode 100644 index 0000000000..cb509f02c1 --- /dev/null +++ b/core-js/internals/async-iterator-prototype.js @@ -0,0 +1,37 @@ +var global = require('../internals/global'); +var shared = require('../internals/shared-store'); +var getPrototypeOf = require('../internals/object-get-prototype-of'); +var has = require('../internals/has'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var wellKnownSymbol = require('../internals/well-known-symbol'); +var IS_PURE = require('../internals/is-pure'); + +var USE_FUNCTION_CONSTRUCTOR = 'USE_FUNCTION_CONSTRUCTOR'; +var ASYNC_ITERATOR = wellKnownSymbol('asyncIterator'); +var AsyncIterator = global.AsyncIterator; +var PassedAsyncIteratorPrototype = shared.AsyncIteratorPrototype; +var AsyncIteratorPrototype, prototype; + +if (!IS_PURE) { + if (PassedAsyncIteratorPrototype) { + AsyncIteratorPrototype = PassedAsyncIteratorPrototype; + } else if (typeof AsyncIterator == 'function') { + AsyncIteratorPrototype = AsyncIterator.prototype; + } else if (shared[USE_FUNCTION_CONSTRUCTOR] || global[USE_FUNCTION_CONSTRUCTOR]) { + try { + // eslint-disable-next-line no-new-func + prototype = getPrototypeOf(getPrototypeOf(getPrototypeOf(Function('return async function*(){}()')()))); + if (getPrototypeOf(prototype) === Object.prototype) AsyncIteratorPrototype = prototype; + } catch (error) { /* empty */ } + } +} + +if (!AsyncIteratorPrototype) AsyncIteratorPrototype = {}; + +if (!has(AsyncIteratorPrototype, ASYNC_ITERATOR)) { + createNonEnumerableProperty(AsyncIteratorPrototype, ASYNC_ITERATOR, function () { + return this; + }); +} + +module.exports = AsyncIteratorPrototype; diff --git a/core-js/internals/create-async-iterator-proxy.js b/core-js/internals/create-async-iterator-proxy.js new file mode 100644 index 0000000000..9e8c5dfe90 --- /dev/null +++ b/core-js/internals/create-async-iterator-proxy.js @@ -0,0 +1,61 @@ +'use strict'; +var path = require('../internals/path'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var create = require('../internals/object-create'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var redefineAll = require('../internals/redefine-all'); +var wellKnownSymbol = require('../internals/well-known-symbol'); +var InternalStateModule = require('../internals/internal-state'); +var getBuiltIn = require('../internals/get-built-in'); + +var Promise = getBuiltIn('Promise'); + +var setInternalState = InternalStateModule.set; +var getInternalState = InternalStateModule.get; + +var TO_STRING_TAG = wellKnownSymbol('toStringTag'); + +var $return = function (value) { + var iterator = getInternalState(this).iterator; + var $$return = iterator['return']; + return $$return === undefined + ? Promise.resolve({ done: true, value: value }) + : anObject($$return.call(iterator, value)); +}; + +var $throw = function (value) { + var iterator = getInternalState(this).iterator; + var $$throw = iterator['throw']; + return $$throw === undefined + ? Promise.reject(value) + : $$throw.call(iterator, value); +}; + +module.exports = function (nextHandler, IS_ITERATOR) { + var AsyncIteratorProxy = function AsyncIterator(state) { + state.next = aFunction(state.iterator.next); + state.done = false; + setInternalState(this, state); + }; + + AsyncIteratorProxy.prototype = redefineAll(create(path.AsyncIterator.prototype), { + next: function next(arg) { + var state = getInternalState(this); + if (state.done) return Promise.resolve({ done: true, value: undefined }); + try { + return Promise.resolve(anObject(nextHandler.call(state, arg, Promise))); + } catch (error) { + return Promise.reject(error); + } + }, + 'return': $return, + 'throw': $throw + }); + + if (!IS_ITERATOR) { + createNonEnumerableProperty(AsyncIteratorProxy.prototype, TO_STRING_TAG, 'Generator'); + } + + return AsyncIteratorProxy; +}; diff --git a/core-js/internals/create-iterator-proxy.js b/core-js/internals/create-iterator-proxy.js new file mode 100644 index 0000000000..951db73219 --- /dev/null +++ b/core-js/internals/create-iterator-proxy.js @@ -0,0 +1,51 @@ +'use strict'; +var path = require('../internals/path'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var create = require('../internals/object-create'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var redefineAll = require('../internals/redefine-all'); +var wellKnownSymbol = require('../internals/well-known-symbol'); +var InternalStateModule = require('../internals/internal-state'); + +var setInternalState = InternalStateModule.set; +var getInternalState = InternalStateModule.get; + +var TO_STRING_TAG = wellKnownSymbol('toStringTag'); + +var $return = function (value) { + var iterator = getInternalState(this).iterator; + var $$return = iterator['return']; + return $$return === undefined ? { done: true, value: value } : anObject($$return.call(iterator, value)); +}; + +var $throw = function (value) { + var iterator = getInternalState(this).iterator; + var $$throw = iterator['throw']; + if ($$throw === undefined) throw value; + return $$throw.call(iterator, value); +}; + +module.exports = function (nextHandler, IS_ITERATOR) { + var IteratorProxy = function Iterator(state) { + state.next = aFunction(state.iterator.next); + state.done = false; + setInternalState(this, state); + }; + + IteratorProxy.prototype = redefineAll(create(path.Iterator.prototype), { + next: function next() { + var state = getInternalState(this); + var result = state.done ? undefined : nextHandler.apply(state, arguments); + return { done: state.done, value: result }; + }, + 'return': $return, + 'throw': $throw + }); + + if (!IS_ITERATOR) { + createNonEnumerableProperty(IteratorProxy.prototype, TO_STRING_TAG, 'Generator'); + } + + return IteratorProxy; +}; diff --git a/core-js/internals/hide.js b/core-js/internals/create-non-enumerable-property.js similarity index 100% rename from core-js/internals/hide.js rename to core-js/internals/create-non-enumerable-property.js diff --git a/core-js/internals/define-iterator.js b/core-js/internals/define-iterator.js index f5acd4f0f1..7f829bf9b9 100644 --- a/core-js/internals/define-iterator.js +++ b/core-js/internals/define-iterator.js @@ -4,7 +4,7 @@ var createIteratorConstructor = require('../internals/create-iterator-constructo var getPrototypeOf = require('../internals/object-get-prototype-of'); var setPrototypeOf = require('../internals/object-set-prototype-of'); var setToStringTag = require('../internals/set-to-string-tag'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var redefine = require('../internals/redefine'); var wellKnownSymbol = require('../internals/well-known-symbol'); var IS_PURE = require('../internals/is-pure'); @@ -51,7 +51,7 @@ module.exports = function (Iterable, NAME, IteratorConstructor, next, DEFAULT, I if (setPrototypeOf) { setPrototypeOf(CurrentIteratorPrototype, IteratorPrototype); } else if (typeof CurrentIteratorPrototype[ITERATOR] != 'function') { - hide(CurrentIteratorPrototype, ITERATOR, returnThis); + createNonEnumerableProperty(CurrentIteratorPrototype, ITERATOR, returnThis); } } // Set @@toStringTag to native iterators @@ -68,7 +68,7 @@ module.exports = function (Iterable, NAME, IteratorConstructor, next, DEFAULT, I // define iterator if ((!IS_PURE || FORCED) && IterablePrototype[ITERATOR] !== defaultIterator) { - hide(IterablePrototype, ITERATOR, defaultIterator); + createNonEnumerableProperty(IterablePrototype, ITERATOR, defaultIterator); } Iterators[NAME] = defaultIterator; diff --git a/core-js/internals/export.js b/core-js/internals/export.js index 324e804133..1d91c57327 100644 --- a/core-js/internals/export.js +++ b/core-js/internals/export.js @@ -1,6 +1,6 @@ var global = require('../internals/global'); var getOwnPropertyDescriptor = require('../internals/object-get-own-property-descriptor').f; -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var redefine = require('../internals/redefine'); var setGlobal = require('../internals/set-global'); var copyConstructorProperties = require('../internals/copy-constructor-properties'); @@ -46,7 +46,7 @@ module.exports = function (options, source) { } // add a flag to not completely full polyfills if (options.sham || (targetProperty && targetProperty.sham)) { - hide(sourceProperty, 'sham', true); + createNonEnumerableProperty(sourceProperty, 'sham', true); } // extend global redefine(target, key, sourceProperty, options); diff --git a/core-js/internals/fix-regexp-well-known-symbol-logic.js b/core-js/internals/fix-regexp-well-known-symbol-logic.js index 563baa3c2e..04b9f2dd4b 100644 --- a/core-js/internals/fix-regexp-well-known-symbol-logic.js +++ b/core-js/internals/fix-regexp-well-known-symbol-logic.js @@ -1,5 +1,5 @@ 'use strict'; -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var redefine = require('../internals/redefine'); var fails = require('../internals/fails'); var wellKnownSymbol = require('../internals/well-known-symbol'); @@ -44,15 +44,22 @@ module.exports = function (KEY, length, exec, sham) { // Symbol-named RegExp methods call .exec var execCalled = false; var re = /a/; - re.exec = function () { execCalled = true; return null; }; if (KEY === 'split') { + // We can't use real regex here since it causes deoptimization + // and serious performance degradation in V8 + // https://github.com/zloirock/core-js/issues/306 + re = {}; // RegExp[@@split] doesn't call the regex's exec method, but first creates // a new one. We need to return the patched regex when creating the new one. re.constructor = {}; re.constructor[SPECIES] = function () { return re; }; + re.flags = ''; + re[SYMBOL] = /./[SYMBOL]; } + re.exec = function () { execCalled = true; return null; }; + re[SYMBOL](''); return !execCalled; }); @@ -88,6 +95,6 @@ module.exports = function (KEY, length, exec, sham) { // 21.2.5.9 RegExp.prototype[@@search](string) : function (string) { return regexMethod.call(string, this); } ); - if (sham) hide(RegExp.prototype[SYMBOL], 'sham', true); + if (sham) createNonEnumerableProperty(RegExp.prototype[SYMBOL], 'sham', true); } }; diff --git a/core-js/internals/get-async-iterator-method.js b/core-js/internals/get-async-iterator-method.js new file mode 100644 index 0000000000..0821b084c6 --- /dev/null +++ b/core-js/internals/get-async-iterator-method.js @@ -0,0 +1,9 @@ +var getIteratorMethod = require('../internals/get-iterator-method'); +var wellKnownSymbol = require('../internals/well-known-symbol'); + +var ASYNC_ITERATOR = wellKnownSymbol('asyncIterator'); + +module.exports = function (it) { + var method = it[ASYNC_ITERATOR]; + return method === undefined ? getIteratorMethod(it) : method; +}; diff --git a/core-js/internals/global.js b/core-js/internals/global.js index bb744f126f..cf00638f1a 100644 --- a/core-js/internals/global.js +++ b/core-js/internals/global.js @@ -1,4 +1,3 @@ -var O = 'object'; var check = function (it) { return it && it.Math == Math && it; }; @@ -6,9 +5,9 @@ var check = function (it) { // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 module.exports = // eslint-disable-next-line no-undef - check(typeof globalThis == O && globalThis) || - check(typeof window == O && window) || - check(typeof self == O && self) || - check(typeof global == O && global) || + check(typeof globalThis == 'object' && globalThis) || + check(typeof window == 'object' && window) || + check(typeof self == 'object' && self) || + check(typeof global == 'object' && global) || // eslint-disable-next-line no-new-func Function('return this')(); diff --git a/core-js/internals/internal-state.js b/core-js/internals/internal-state.js index 9a401750b9..18c402d5b3 100644 --- a/core-js/internals/internal-state.js +++ b/core-js/internals/internal-state.js @@ -1,7 +1,7 @@ var NATIVE_WEAK_MAP = require('../internals/native-weak-map'); var global = require('../internals/global'); var isObject = require('../internals/is-object'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var objectHas = require('../internals/has'); var sharedKey = require('../internals/shared-key'); var hiddenKeys = require('../internals/hidden-keys'); @@ -41,7 +41,7 @@ if (NATIVE_WEAK_MAP) { var STATE = sharedKey('state'); hiddenKeys[STATE] = true; set = function (it, metadata) { - hide(it, STATE, metadata); + createNonEnumerableProperty(it, STATE, metadata); return metadata; }; get = function (it) { diff --git a/core-js/internals/iterate.js b/core-js/internals/iterate.js index 49ec4cb7db..851c96b1e3 100644 --- a/core-js/internals/iterate.js +++ b/core-js/internals/iterate.js @@ -12,7 +12,7 @@ var Result = function (stopped, result) { var iterate = module.exports = function (iterable, fn, that, AS_ENTRIES, IS_ITERATOR) { var boundFunction = bind(fn, that, AS_ENTRIES ? 2 : 1); - var iterator, iterFn, index, length, result, step; + var iterator, iterFn, index, length, result, next, step; if (IS_ITERATOR) { iterator = iterable; @@ -31,9 +31,10 @@ var iterate = module.exports = function (iterable, fn, that, AS_ENTRIES, IS_ITER iterator = iterFn.call(iterable); } - while (!(step = iterator.next()).done) { + next = iterator.next; + while (!(step = next.call(iterator)).done) { result = callWithSafeIterationClosing(iterator, boundFunction, step.value, AS_ENTRIES); - if (result && result instanceof Result) return result; + if (typeof result == 'object' && result && result instanceof Result) return result; } return new Result(false); }; diff --git a/core-js/internals/iterators-core.js b/core-js/internals/iterators-core.js index bdc5e53e1e..fccca93e91 100644 --- a/core-js/internals/iterators-core.js +++ b/core-js/internals/iterators-core.js @@ -1,6 +1,6 @@ 'use strict'; var getPrototypeOf = require('../internals/object-get-prototype-of'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var has = require('../internals/has'); var wellKnownSymbol = require('../internals/well-known-symbol'); var IS_PURE = require('../internals/is-pure'); @@ -27,7 +27,9 @@ if ([].keys) { if (IteratorPrototype == undefined) IteratorPrototype = {}; // 25.1.2.1.1 %IteratorPrototype%[@@iterator]() -if (!IS_PURE && !has(IteratorPrototype, ITERATOR)) hide(IteratorPrototype, ITERATOR, returnThis); +if (!IS_PURE && !has(IteratorPrototype, ITERATOR)) { + createNonEnumerableProperty(IteratorPrototype, ITERATOR, returnThis); +} module.exports = { IteratorPrototype: IteratorPrototype, diff --git a/core-js/internals/map-upsert.js b/core-js/internals/map-upsert.js new file mode 100644 index 0000000000..2c51e78b9a --- /dev/null +++ b/core-js/internals/map-upsert.js @@ -0,0 +1,23 @@ +'use strict'; +var anObject = require('../internals/an-object'); + +// `Map.prototype.upsert` method +// https://github.com/thumbsupep/proposal-upsert +module.exports = function upsert(key, updateFn /* , insertFn */) { + var map = anObject(this); + var insertFn = arguments.length > 2 ? arguments[2] : undefined; + var value; + if (typeof updateFn != 'function' && typeof insertFn != 'function') { + throw TypeError('At least one callback required'); + } + if (map.has(key)) { + value = map.get(key); + if (typeof updateFn == 'function') { + value = updateFn(value); + map.set(key, value); + } + } else if (typeof insertFn == 'function') { + value = insertFn(); + map.set(key, value); + } return value; +}; diff --git a/core-js/internals/microtask.js b/core-js/internals/microtask.js index 849fb952a0..1a3cd74fb3 100644 --- a/core-js/internals/microtask.js +++ b/core-js/internals/microtask.js @@ -42,7 +42,7 @@ if (!queueMicrotask) { } else if (MutationObserver && !/(iphone|ipod|ipad).*applewebkit/i.test(userAgent)) { toggle = true; node = document.createTextNode(''); - new MutationObserver(flush).observe(node, { characterData: true }); // eslint-disable-line no-new + new MutationObserver(flush).observe(node, { characterData: true }); notify = function () { node.data = toggle = !toggle; }; diff --git a/core-js/internals/native-url.js b/core-js/internals/native-url.js index ce353aa726..b9ac5878db 100644 --- a/core-js/internals/native-url.js +++ b/core-js/internals/native-url.js @@ -5,13 +5,18 @@ var IS_PURE = require('../internals/is-pure'); var ITERATOR = wellKnownSymbol('iterator'); module.exports = !fails(function () { - var url = new URL('b?e=1', 'http://a'); + var url = new URL('b?a=1&b=2&c=3', 'http://a'); var searchParams = url.searchParams; + var result = ''; url.pathname = 'c%20d'; + searchParams.forEach(function (value, key) { + searchParams['delete']('b'); + result += key + value; + }); return (IS_PURE && !url.toJSON) || !searchParams.sort - || url.href !== 'http://a/c%20d?e=1' - || searchParams.get('e') !== '1' + || url.href !== 'http://a/c%20d?a=1&c=3' + || searchParams.get('c') !== '3' || String(new URLSearchParams('?a=1')) !== 'a=1' || !searchParams[ITERATOR] // throws in Edge @@ -20,5 +25,9 @@ module.exports = !fails(function () { // not punycoded in Edge || new URL('http://тест').host !== 'xn--e1aybc' // not escaped in Chrome 62- - || new URL('http://a#б').hash !== '#%D0%B1'; + || new URL('http://a#б').hash !== '#%D0%B1' + // fails in Chrome 66- + || result !== 'a1c3' + // throws in Safari + || new URL('http://x', undefined).host !== 'x'; }); diff --git a/core-js/internals/redefine.js b/core-js/internals/redefine.js index 62f842a5e4..76f24c627e 100644 --- a/core-js/internals/redefine.js +++ b/core-js/internals/redefine.js @@ -1,6 +1,6 @@ var global = require('../internals/global'); var shared = require('../internals/shared'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var has = require('../internals/has'); var setGlobal = require('../internals/set-global'); var nativeFunctionToString = require('../internals/function-to-string'); @@ -19,7 +19,7 @@ shared('inspectSource', function (it) { var simple = options ? !!options.enumerable : false; var noTargetGet = options ? !!options.noTargetGet : false; if (typeof value == 'function') { - if (typeof key == 'string' && !has(value, 'name')) hide(value, 'name', key); + if (typeof key == 'string' && !has(value, 'name')) createNonEnumerableProperty(value, 'name', key); enforceInternalState(value).source = TEMPLATE.join(typeof key == 'string' ? key : ''); } if (O === global) { @@ -32,7 +32,7 @@ shared('inspectSource', function (it) { simple = true; } if (simple) O[key] = value; - else hide(O, key, value); + else createNonEnumerableProperty(O, key, value); // add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative })(Function.prototype, 'toString', function toString() { return typeof this == 'function' && getInternalState(this).source || nativeFunctionToString.call(this); diff --git a/core-js/internals/set-global.js b/core-js/internals/set-global.js index 6ebbe5db60..8ac96010e6 100644 --- a/core-js/internals/set-global.js +++ b/core-js/internals/set-global.js @@ -1,9 +1,9 @@ var global = require('../internals/global'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); module.exports = function (key, value) { try { - hide(global, key, value); + createNonEnumerableProperty(global, key, value); } catch (error) { global[key] = value; } return value; diff --git a/core-js/internals/shared-store.js b/core-js/internals/shared-store.js new file mode 100644 index 0000000000..c54550a880 --- /dev/null +++ b/core-js/internals/shared-store.js @@ -0,0 +1,7 @@ +var global = require('../internals/global'); +var setGlobal = require('../internals/set-global'); + +var SHARED = '__core-js_shared__'; +var store = global[SHARED] || setGlobal(SHARED, {}); + +module.exports = store; diff --git a/core-js/internals/shared.js b/core-js/internals/shared.js index 2417549c3d..41fe927fd9 100644 --- a/core-js/internals/shared.js +++ b/core-js/internals/shared.js @@ -1,14 +1,10 @@ -var global = require('../internals/global'); -var setGlobal = require('../internals/set-global'); var IS_PURE = require('../internals/is-pure'); - -var SHARED = '__core-js_shared__'; -var store = global[SHARED] || setGlobal(SHARED, {}); +var store = require('../internals/shared-store'); (module.exports = function (key, value) { return store[key] || (store[key] = value !== undefined ? value : {}); })('versions', []).push({ - version: '3.2.1', + version: '3.3.5', mode: IS_PURE ? 'pure' : 'global', copyright: '© 2019 Denis Pushkarev (zloirock.ru)' }); diff --git a/core-js/internals/task.js b/core-js/internals/task.js index 7ea4fc4a7b..bddba5be12 100644 --- a/core-js/internals/task.js +++ b/core-js/internals/task.js @@ -4,6 +4,7 @@ var classof = require('../internals/classof-raw'); var bind = require('../internals/bind-context'); var html = require('../internals/html'); var createElement = require('../internals/document-create-element'); +var userAgent = require('../internals/user-agent'); var location = global.location; var set = global.setImmediate; @@ -67,7 +68,8 @@ if (!set || !clear) { Dispatch.now(runner(id)); }; // Browsers with MessageChannel, includes WebWorkers - } else if (MessageChannel) { + // except iOS - https://github.com/zloirock/core-js/issues/624 + } else if (MessageChannel && !/(iphone|ipod|ipad).*applewebkit/i.test(userAgent)) { channel = new MessageChannel(); port = channel.port2; channel.port1.onmessage = listener; diff --git a/core-js/internals/to-offset.js b/core-js/internals/to-offset.js index 9f3e5f23ab..8c511fcd95 100644 --- a/core-js/internals/to-offset.js +++ b/core-js/internals/to-offset.js @@ -1,7 +1,7 @@ -var toInteger = require('../internals/to-integer'); +var toPositiveInteger = require('../internals/to-positive-integer'); module.exports = function (it, BYTES) { - var offset = toInteger(it); - if (offset < 0 || offset % BYTES) throw RangeError('Wrong offset'); + var offset = toPositiveInteger(it); + if (offset % BYTES) throw RangeError('Wrong offset'); return offset; }; diff --git a/core-js/internals/to-positive-integer.js b/core-js/internals/to-positive-integer.js new file mode 100644 index 0000000000..c92aca5eb3 --- /dev/null +++ b/core-js/internals/to-positive-integer.js @@ -0,0 +1,7 @@ +var toInteger = require('../internals/to-integer'); + +module.exports = function (it) { + var result = toInteger(it); + if (result < 0) throw RangeError("The argument can't be less than 0"); + return result; +}; diff --git a/core-js/internals/typed-array-constructor.js b/core-js/internals/typed-array-constructor.js index 82c3f9d525..aacbf04a07 100644 --- a/core-js/internals/typed-array-constructor.js +++ b/core-js/internals/typed-array-constructor.js @@ -7,7 +7,7 @@ var ArrayBufferViewCore = require('../internals/array-buffer-view-core'); var ArrayBufferModule = require('../internals/array-buffer'); var anInstance = require('../internals/an-instance'); var createPropertyDescriptor = require('../internals/create-property-descriptor'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var toLength = require('../internals/to-length'); var toIndex = require('../internals/to-index'); var toOffset = require('../internals/to-offset'); @@ -105,7 +105,6 @@ if (DESCRIPTORS) { defineProperty: wrappedDefineProperty }); - // eslint-disable-next-line max-statements module.exports = function (TYPE, BYTES, wrapper, CLAMPED) { var CONSTRUCTOR_NAME = TYPE + (CLAMPED ? 'Clamped' : '') + 'Array'; var GETTER = 'get' + TYPE; @@ -193,16 +192,20 @@ if (DESCRIPTORS) { if (setPrototypeOf) setPrototypeOf(TypedArrayConstructor, TypedArray); forEach(getOwnPropertyNames(NativeTypedArrayConstructor), function (key) { - if (!(key in TypedArrayConstructor)) hide(TypedArrayConstructor, key, NativeTypedArrayConstructor[key]); + if (!(key in TypedArrayConstructor)) { + createNonEnumerableProperty(TypedArrayConstructor, key, NativeTypedArrayConstructor[key]); + } }); TypedArrayConstructor.prototype = TypedArrayConstructorPrototype; } if (TypedArrayConstructorPrototype.constructor !== TypedArrayConstructor) { - hide(TypedArrayConstructorPrototype, 'constructor', TypedArrayConstructor); + createNonEnumerableProperty(TypedArrayConstructorPrototype, 'constructor', TypedArrayConstructor); } - if (TYPED_ARRAY_TAG) hide(TypedArrayConstructorPrototype, TYPED_ARRAY_TAG, CONSTRUCTOR_NAME); + if (TYPED_ARRAY_TAG) { + createNonEnumerableProperty(TypedArrayConstructorPrototype, TYPED_ARRAY_TAG, CONSTRUCTOR_NAME); + } exported[CONSTRUCTOR_NAME] = TypedArrayConstructor; @@ -211,11 +214,11 @@ if (DESCRIPTORS) { }, exported); if (!(BYTES_PER_ELEMENT in TypedArrayConstructor)) { - hide(TypedArrayConstructor, BYTES_PER_ELEMENT, BYTES); + createNonEnumerableProperty(TypedArrayConstructor, BYTES_PER_ELEMENT, BYTES); } if (!(BYTES_PER_ELEMENT in TypedArrayConstructorPrototype)) { - hide(TypedArrayConstructorPrototype, BYTES_PER_ELEMENT, BYTES); + createNonEnumerableProperty(TypedArrayConstructorPrototype, BYTES_PER_ELEMENT, BYTES); } setSpecies(CONSTRUCTOR_NAME); diff --git a/core-js/internals/typed-array-from.js b/core-js/internals/typed-array-from.js index 28d6e3456e..8a4debdf54 100644 --- a/core-js/internals/typed-array-from.js +++ b/core-js/internals/typed-array-from.js @@ -11,11 +11,12 @@ module.exports = function from(source /* , mapfn, thisArg */) { var mapfn = argumentsLength > 1 ? arguments[1] : undefined; var mapping = mapfn !== undefined; var iteratorMethod = getIteratorMethod(O); - var i, length, result, step, iterator; + var i, length, result, step, iterator, next; if (iteratorMethod != undefined && !isArrayIteratorMethod(iteratorMethod)) { iterator = iteratorMethod.call(O); + next = iterator.next; O = []; - while (!(step = iterator.next()).done) { + while (!(step = next.call(iterator)).done) { O.push(step.value); } } diff --git a/core-js/internals/v8-version.js b/core-js/internals/v8-version.js new file mode 100644 index 0000000000..39f1d1ab17 --- /dev/null +++ b/core-js/internals/v8-version.js @@ -0,0 +1,17 @@ +var global = require('../internals/global'); +var userAgent = require('../internals/user-agent'); + +var process = global.process; +var versions = process && process.versions; +var v8 = versions && versions.v8; +var match, version; + +if (v8) { + match = v8.split('.'); + version = match[0] + match[1]; +} else if (userAgent) { + match = userAgent.match(/Chrome\/(\d+)/); + if (match) version = match[1]; +} + +module.exports = version && +version; diff --git a/core-js/modules/es.array.concat.js b/core-js/modules/es.array.concat.js index c819dd58ed..d850844ae5 100644 --- a/core-js/modules/es.array.concat.js +++ b/core-js/modules/es.array.concat.js @@ -9,12 +9,16 @@ var createProperty = require('../internals/create-property'); var arraySpeciesCreate = require('../internals/array-species-create'); var arrayMethodHasSpeciesSupport = require('../internals/array-method-has-species-support'); var wellKnownSymbol = require('../internals/well-known-symbol'); +var V8_VERSION = require('../internals/v8-version'); var IS_CONCAT_SPREADABLE = wellKnownSymbol('isConcatSpreadable'); var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF; var MAXIMUM_ALLOWED_INDEX_EXCEEDED = 'Maximum allowed index exceeded'; -var IS_CONCAT_SPREADABLE_SUPPORT = !fails(function () { +// We can't use this feature detection in V8 since it causes +// deoptimization and serious performance degradation +// https://github.com/zloirock/core-js/issues/679 +var IS_CONCAT_SPREADABLE_SUPPORT = V8_VERSION >= 51 || !fails(function () { var array = []; array[IS_CONCAT_SPREADABLE] = false; return array.concat()[0] !== array; diff --git a/core-js/modules/es.array.reverse.js b/core-js/modules/es.array.reverse.js index 16ea40e91c..bfe96d55a8 100644 --- a/core-js/modules/es.array.reverse.js +++ b/core-js/modules/es.array.reverse.js @@ -11,6 +11,7 @@ var test = [1, 2]; // https://bugs.webkit.org/show_bug.cgi?id=188794 $({ target: 'Array', proto: true, forced: String(test) === String(test.reverse()) }, { reverse: function reverse() { + // eslint-disable-next-line no-self-assign if (isArray(this)) this.length = this.length; return nativeReverse.call(this); } diff --git a/core-js/modules/es.date.to-primitive.js b/core-js/modules/es.date.to-primitive.js index 7a308279ac..68dc59f58c 100644 --- a/core-js/modules/es.date.to-primitive.js +++ b/core-js/modules/es.date.to-primitive.js @@ -1,4 +1,4 @@ -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var dateToPrimitive = require('../internals/date-to-primitive'); var wellKnownSymbol = require('../internals/well-known-symbol'); @@ -7,4 +7,6 @@ var DatePrototype = Date.prototype; // `Date.prototype[@@toPrimitive]` method // https://tc39.github.io/ecma262/#sec-date.prototype-@@toprimitive -if (!(TO_PRIMITIVE in DatePrototype)) hide(DatePrototype, TO_PRIMITIVE, dateToPrimitive); +if (!(TO_PRIMITIVE in DatePrototype)) { + createNonEnumerableProperty(DatePrototype, TO_PRIMITIVE, dateToPrimitive); +} diff --git a/core-js/modules/es.global-this.js b/core-js/modules/es.global-this.js new file mode 100644 index 0000000000..7e925c8753 --- /dev/null +++ b/core-js/modules/es.global-this.js @@ -0,0 +1,8 @@ +var $ = require('../internals/export'); +var global = require('../internals/global'); + +// `globalThis` object +// https://github.com/tc39/proposal-global +$({ global: true }, { + globalThis: global +}); diff --git a/core-js/modules/es.promise.js b/core-js/modules/es.promise.js index b0d76c63aa..ee77626d3e 100644 --- a/core-js/modules/es.promise.js +++ b/core-js/modules/es.promise.js @@ -2,7 +2,7 @@ var $ = require('../internals/export'); var IS_PURE = require('../internals/is-pure'); var global = require('../internals/global'); -var path = require('../internals/path'); +var getBuiltIn = require('../internals/get-built-in'); var NativePromise = require('../internals/native-promise-constructor'); var redefine = require('../internals/redefine'); var redefineAll = require('../internals/redefine-all'); @@ -21,10 +21,10 @@ var promiseResolve = require('../internals/promise-resolve'); var hostReportErrors = require('../internals/host-report-errors'); var newPromiseCapabilityModule = require('../internals/new-promise-capability'); var perform = require('../internals/perform'); -var userAgent = require('../internals/user-agent'); var InternalStateModule = require('../internals/internal-state'); var isForced = require('../internals/is-forced'); var wellKnownSymbol = require('../internals/well-known-symbol'); +var V8_VERSION = require('../internals/v8-version'); var SPECIES = wellKnownSymbol('species'); var PROMISE = 'Promise'; @@ -35,9 +35,7 @@ var PromiseConstructor = NativePromise; var TypeError = global.TypeError; var document = global.document; var process = global.process; -var $fetch = global.fetch; -var versions = process && process.versions; -var v8 = versions && versions.v8 || ''; +var $fetch = getBuiltIn('fetch'); var newPromiseCapability = newPromiseCapabilityModule.f; var newGenericPromiseCapability = newPromiseCapability; var IS_NODE = classof(process) == 'process'; @@ -52,21 +50,26 @@ var UNHANDLED = 2; var Internal, OwnPromiseCapability, PromiseWrapper, nativeThen; var FORCED = isForced(PROMISE, function () { - // correct subclassing with @@species support + // V8 6.6 (Node 10 and Chrome 66) have a bug with resolving custom thenables + // https://bugs.chromium.org/p/chromium/issues/detail?id=830565 + // We can't detect it synchronously, so just check versions + if (V8_VERSION === 66) return true; + // Unhandled rejections tracking support, NodeJS Promise without it fails @@species test + if (!IS_NODE && typeof PromiseRejectionEvent != 'function') return true; + // We need Promise#finally in the pure version for preventing prototype pollution + if (IS_PURE && !PromiseConstructor.prototype['finally']) return true; + // We can't use @@species feature detection in V8 since it causes + // deoptimization and performance degradation + // https://github.com/zloirock/core-js/issues/679 + if (V8_VERSION >= 51 && /native code/.test(PromiseConstructor)) return false; + // Detect correctness of subclassing with @@species support var promise = PromiseConstructor.resolve(1); - var empty = function () { /* empty */ }; - var FakePromise = (promise.constructor = {})[SPECIES] = function (exec) { - exec(empty, empty); + var FakePromise = function (exec) { + exec(function () { /* empty */ }, function () { /* empty */ }); }; - // unhandled rejections tracking support, NodeJS Promise without it fails @@species test - return !((IS_NODE || typeof PromiseRejectionEvent == 'function') - && (!IS_PURE || promise['finally']) - && promise.then(empty) instanceof FakePromise - // v8 6.6 (Node 10 and Chrome 66) have a bug with resolving custom thenables - // https://bugs.chromium.org/p/chromium/issues/detail?id=830565 - // we can't detect it synchronously, so just check versions - && v8.indexOf('6.6') !== 0 - && userAgent.indexOf('Chrome/66') === -1); + var constructor = promise.constructor = {}; + constructor[SPECIES] = FakePromise; + return !(promise.then(function () { /* empty */ }) instanceof FakePromise); }); var INCORRECT_ITERATION = FORCED || !checkCorrectnessOfIteration(function (iterable) { @@ -283,12 +286,13 @@ if (FORCED) { return new PromiseConstructor(function (resolve, reject) { nativeThen.call(that, resolve, reject); }).then(onFulfilled, onRejected); - }); + // https://github.com/zloirock/core-js/issues/640 + }, { unsafe: true }); // wrap fetch result if (typeof $fetch == 'function') $({ global: true, enumerable: true, forced: true }, { // eslint-disable-next-line no-unused-vars - fetch: function fetch(input) { + fetch: function fetch(input /* , init */) { return promiseResolve(PromiseConstructor, $fetch.apply(global, arguments)); } }); @@ -302,7 +306,7 @@ $({ global: true, wrap: true, forced: FORCED }, { setToStringTag(PromiseConstructor, PROMISE, false, true); setSpecies(PROMISE); -PromiseWrapper = path[PROMISE]; +PromiseWrapper = getBuiltIn(PROMISE); // statics $({ target: PROMISE, stat: true, forced: FORCED }, { diff --git a/core-js/modules/es.string.match-all.js b/core-js/modules/es.string.match-all.js index 53bcc7bb03..f968f4a17a 100644 --- a/core-js/modules/es.string.match-all.js +++ b/core-js/modules/es.string.match-all.js @@ -6,8 +6,10 @@ var toLength = require('../internals/to-length'); var aFunction = require('../internals/a-function'); var anObject = require('../internals/an-object'); var classof = require('../internals/classof'); -var getFlags = require('../internals/regexp-flags'); -var hide = require('../internals/hide'); +var isRegExp = require('../internals/is-regexp'); +var getRegExpFlags = require('../internals/regexp-flags'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var fails = require('../internals/fails'); var wellKnownSymbol = require('../internals/well-known-symbol'); var speciesConstructor = require('../internals/species-constructor'); var advanceStringIndex = require('../internals/advance-string-index'); @@ -21,6 +23,11 @@ var setInternalState = InternalStateModule.set; var getInternalState = InternalStateModule.getterFor(REGEXP_STRING_ITERATOR); var RegExpPrototype = RegExp.prototype; var regExpBuiltinExec = RegExpPrototype.exec; +var nativeMatchAll = ''.matchAll; + +var WORKS_WITH_NON_GLOBAL_REGEX = !!nativeMatchAll && !fails(function () { + 'a'.matchAll(/./); +}); var regExpExec = function (R, S) { var exec = R.exec; @@ -64,7 +71,7 @@ var $matchAll = function (string) { C = speciesConstructor(R, RegExp); flagsValue = R.flags; if (flagsValue === undefined && R instanceof RegExp && !('flags' in RegExpPrototype)) { - flagsValue = getFlags.call(R); + flagsValue = getRegExpFlags.call(R); } flags = flagsValue === undefined ? '' : String(flagsValue); matcher = new C(C === RegExp ? R.source : R, flags); @@ -76,19 +83,27 @@ var $matchAll = function (string) { // `String.prototype.matchAll` method // https://github.com/tc39/proposal-string-matchall -$({ target: 'String', proto: true }, { +$({ target: 'String', proto: true, forced: WORKS_WITH_NON_GLOBAL_REGEX }, { matchAll: function matchAll(regexp) { var O = requireObjectCoercible(this); - var S, matcher, rx; + var flags, S, matcher, rx; if (regexp != null) { + if (isRegExp(regexp)) { + flags = String(requireObjectCoercible('flags' in RegExpPrototype + ? regexp.flags + : getRegExpFlags.call(regexp) + )); + if (!~flags.indexOf('g')) throw TypeError('`.matchAll` does not allow non-global regexes'); + } + if (WORKS_WITH_NON_GLOBAL_REGEX) return nativeMatchAll.apply(O, arguments); matcher = regexp[MATCH_ALL]; if (matcher === undefined && IS_PURE && classof(regexp) == 'RegExp') matcher = $matchAll; if (matcher != null) return aFunction(matcher).call(regexp, O); - } + } else if (WORKS_WITH_NON_GLOBAL_REGEX) return nativeMatchAll.apply(O, arguments); S = String(O); rx = new RegExp(regexp, 'g'); return IS_PURE ? $matchAll.call(rx, S) : rx[MATCH_ALL](S); } }); -IS_PURE || MATCH_ALL in RegExpPrototype || hide(RegExpPrototype, MATCH_ALL, $matchAll); +IS_PURE || MATCH_ALL in RegExpPrototype || createNonEnumerableProperty(RegExpPrototype, MATCH_ALL, $matchAll); diff --git a/core-js/modules/es.symbol.js b/core-js/modules/es.symbol.js index 8c06b96601..b53b2f05cd 100644 --- a/core-js/modules/es.symbol.js +++ b/core-js/modules/es.symbol.js @@ -21,7 +21,7 @@ var getOwnPropertySymbolsModule = require('../internals/object-get-own-property- var getOwnPropertyDescriptorModule = require('../internals/object-get-own-property-descriptor'); var definePropertyModule = require('../internals/object-define-property'); var propertyIsEnumerableModule = require('../internals/object-property-is-enumerable'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var redefine = require('../internals/redefine'); var shared = require('../internals/shared'); var sharedKey = require('../internals/shared-key'); @@ -291,7 +291,9 @@ JSON && $({ target: 'JSON', stat: true, forced: !NATIVE_SYMBOL || fails(function // `Symbol.prototype[@@toPrimitive]` method // https://tc39.github.io/ecma262/#sec-symbol.prototype-@@toprimitive -if (!$Symbol[PROTOTYPE][TO_PRIMITIVE]) hide($Symbol[PROTOTYPE], TO_PRIMITIVE, $Symbol[PROTOTYPE].valueOf); +if (!$Symbol[PROTOTYPE][TO_PRIMITIVE]) { + createNonEnumerableProperty($Symbol[PROTOTYPE], TO_PRIMITIVE, $Symbol[PROTOTYPE].valueOf); +} // `Symbol.prototype[@@toStringTag]` property // https://tc39.github.io/ecma262/#sec-symbol.prototype-@@tostringtag setToStringTag($Symbol, SYMBOL); diff --git a/core-js/modules/esnext.aggregate-error.js b/core-js/modules/esnext.aggregate-error.js index ec55c4374c..d79a409fe9 100644 --- a/core-js/modules/esnext.aggregate-error.js +++ b/core-js/modules/esnext.aggregate-error.js @@ -1,10 +1,18 @@ +'use strict'; var $ = require('../internals/export'); +var DESCRIPTORS = require('../internals/descriptors'); var getPrototypeOf = require('../internals/object-get-prototype-of'); var setPrototypeOf = require('../internals/object-set-prototype-of'); var create = require('../internals/object-create'); +var defineProperty = require('../internals/object-define-property'); var createPropertyDescriptor = require('../internals/create-property-descriptor'); var iterate = require('../internals/iterate'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var anObject = require('../internals/an-object'); +var InternalStateModule = require('../internals/internal-state'); + +var setInternalState = InternalStateModule.set; +var getInternalAggregateErrorState = InternalStateModule.getterFor('AggregateError'); var $AggregateError = function AggregateError(errors, message) { var that = this; @@ -14,14 +22,30 @@ var $AggregateError = function AggregateError(errors, message) { } var errorsArray = []; iterate(errors, errorsArray.push, errorsArray); - hide(that, 'errors', errorsArray); - if (message !== undefined) hide(that, 'message', String(message)); + if (DESCRIPTORS) setInternalState(that, { errors: errorsArray, type: 'AggregateError' }); + else that.errors = errorsArray; + if (message !== undefined) createNonEnumerableProperty(that, 'message', String(message)); return that; }; $AggregateError.prototype = create(Error.prototype, { constructor: createPropertyDescriptor(5, $AggregateError), - name: createPropertyDescriptor(5, 'AggregateError') + message: createPropertyDescriptor(5, ''), + name: createPropertyDescriptor(5, 'AggregateError'), + toString: createPropertyDescriptor(5, function toString() { + var name = anObject(this).name; + name = name === undefined ? 'AggregateError' : String(name); + var message = this.message; + message = message === undefined ? '' : String(message); + return name + ': ' + message; + }) +}); + +if (DESCRIPTORS) defineProperty.f($AggregateError.prototype, 'errors', { + get: function () { + return getInternalAggregateErrorState(this).errors; + }, + configurable: true }); $({ global: true }, { diff --git a/core-js/modules/esnext.async-iterator.as-indexed-pairs.js b/core-js/modules/esnext.async-iterator.as-indexed-pairs.js new file mode 100644 index 0000000000..d0cb60a441 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.as-indexed-pairs.js @@ -0,0 +1,27 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var anObject = require('../internals/an-object'); +var createAsyncIteratorProxy = require('../internals/create-async-iterator-proxy'); + +var AsyncIteratorProxy = createAsyncIteratorProxy(function (arg, Promise) { + var state = this; + var iterator = state.iterator; + + return Promise.resolve(anObject(state.next.call(iterator, arg))).then(function (step) { + if (anObject(step).done) { + state.done = true; + return { done: true, value: undefined }; + } + return { done: false, value: [state.index++, step.value] }; + }); +}); + +$({ target: 'AsyncIterator', proto: true, real: true }, { + asIndexedPairs: function asIndexedPairs() { + return new AsyncIteratorProxy({ + iterator: anObject(this), + index: 0 + }); + } +}); diff --git a/core-js/modules/esnext.async-iterator.constructor.js b/core-js/modules/esnext.async-iterator.constructor.js new file mode 100644 index 0000000000..5a1d4ad25e --- /dev/null +++ b/core-js/modules/esnext.async-iterator.constructor.js @@ -0,0 +1,29 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var anInstance = require('../internals/an-instance'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var has = require('../internals/has'); +var wellKnownSymbol = require('../internals/well-known-symbol'); +var AsyncIteratorPrototype = require('../internals/async-iterator-prototype'); +var IS_PURE = require('../internals/is-pure'); + +var TO_STRING_TAG = wellKnownSymbol('toStringTag'); + +var AsyncIteratorConstructor = function AsyncIterator() { + anInstance(this, AsyncIteratorConstructor); +}; + +AsyncIteratorConstructor.prototype = AsyncIteratorPrototype; + +if (!has(AsyncIteratorPrototype, TO_STRING_TAG)) { + createNonEnumerableProperty(AsyncIteratorPrototype, TO_STRING_TAG, 'AsyncIterator'); +} + +if (!has(AsyncIteratorPrototype, 'constructor') || AsyncIteratorPrototype.constructor === Object) { + createNonEnumerableProperty(AsyncIteratorPrototype, 'constructor', AsyncIteratorConstructor); +} + +$({ global: true, forced: IS_PURE }, { + AsyncIterator: AsyncIteratorConstructor +}); diff --git a/core-js/modules/esnext.async-iterator.drop.js b/core-js/modules/esnext.async-iterator.drop.js new file mode 100644 index 0000000000..5924f855cc --- /dev/null +++ b/core-js/modules/esnext.async-iterator.drop.js @@ -0,0 +1,41 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var anObject = require('../internals/an-object'); +var toPositiveInteger = require('../internals/to-positive-integer'); +var createAsyncIteratorProxy = require('../internals/create-async-iterator-proxy'); + +var AsyncIteratorProxy = createAsyncIteratorProxy(function (arg, Promise) { + var state = this; + + return new Promise(function (resolve, reject) { + var loop = function () { + try { + Promise.resolve( + anObject(state.next.call(state.iterator, state.remaining ? undefined : arg)) + ).then(function (step) { + try { + if (anObject(step).done) { + state.done = true; + resolve({ done: true, value: undefined }); + } else if (state.remaining) { + state.remaining--; + loop(); + } else resolve({ done: false, value: step.value }); + } catch (err) { reject(err); } + }, reject); + } catch (error) { reject(error); } + }; + + loop(); + }); +}); + +$({ target: 'AsyncIterator', proto: true, real: true }, { + drop: function drop(limit) { + return new AsyncIteratorProxy({ + iterator: anObject(this), + remaining: toPositiveInteger(limit) + }); + } +}); diff --git a/core-js/modules/esnext.async-iterator.every.js b/core-js/modules/esnext.async-iterator.every.js new file mode 100644 index 0000000000..70c945dc78 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.every.js @@ -0,0 +1,10 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var $every = require('../internals/async-iterator-iteration').every; + +$({ target: 'AsyncIterator', proto: true, real: true }, { + every: function every(fn) { + return $every(this, fn); + } +}); diff --git a/core-js/modules/esnext.async-iterator.filter.js b/core-js/modules/esnext.async-iterator.filter.js new file mode 100644 index 0000000000..4861047ef0 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.filter.js @@ -0,0 +1,42 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var createAsyncIteratorProxy = require('../internals/create-async-iterator-proxy'); + +var AsyncIteratorProxy = createAsyncIteratorProxy(function (arg, Promise) { + var state = this; + var filterer = state.filterer; + + return new Promise(function (resolve, reject) { + var loop = function () { + try { + Promise.resolve(anObject(state.next.call(state.iterator, arg))).then(function (step) { + try { + if (anObject(step).done) { + state.done = true; + resolve({ done: true, value: undefined }); + } else { + var value = step.value; + Promise.resolve(filterer(value)).then(function (selected) { + selected ? resolve({ done: false, value: value }) : loop(); + }, reject); + } + } catch (err) { reject(err); } + }, reject); + } catch (error) { reject(error); } + }; + + loop(); + }); +}); + +$({ target: 'AsyncIterator', proto: true, real: true }, { + filter: function filter(filterer) { + return new AsyncIteratorProxy({ + iterator: anObject(this), + filterer: aFunction(filterer) + }); + } +}); diff --git a/core-js/modules/esnext.async-iterator.find.js b/core-js/modules/esnext.async-iterator.find.js new file mode 100644 index 0000000000..0211e51f01 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.find.js @@ -0,0 +1,10 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var $find = require('../internals/async-iterator-iteration').find; + +$({ target: 'AsyncIterator', proto: true, real: true }, { + find: function find(fn) { + return $find(this, fn); + } +}); diff --git a/core-js/modules/esnext.async-iterator.flat-map.js b/core-js/modules/esnext.async-iterator.flat-map.js new file mode 100644 index 0000000000..1ac6fba1a2 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.flat-map.js @@ -0,0 +1,67 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var isObject = require('../internals/is-object'); +var createAsyncIteratorProxy = require('../internals/create-async-iterator-proxy'); +var getAsyncIteratorMethod = require('../internals/get-async-iterator-method'); + +var AsyncIteratorProxy = createAsyncIteratorProxy(function (arg, Promise) { + var state = this; + var mapper = state.mapper; + var innerIterator, iteratorMethod; + + return new Promise(function (resolve, reject) { + var outerLoop = function () { + try { + Promise.resolve(anObject(state.next.call(state.iterator, arg))).then(function (step) { + try { + if (anObject(step).done) { + state.done = true; + resolve({ done: true, value: undefined }); + } else { + Promise.resolve(mapper(step.value)).then(function (mapped) { + try { + if (isObject(mapped) && (iteratorMethod = getAsyncIteratorMethod(mapped)) !== undefined) { + state.innerIterator = innerIterator = iteratorMethod.call(mapped); + state.innerNext = aFunction(innerIterator.next); + return innerLoop(); + } resolve({ done: false, value: mapped }); + } catch (error2) { reject(error2); } + }, reject); + } + } catch (error1) { reject(error1); } + }, reject); + } catch (error) { reject(error); } + }; + + var innerLoop = function () { + if (innerIterator = state.innerIterator) { + try { + Promise.resolve(anObject(state.innerNext.call(innerIterator))).then(function (result) { + try { + if (anObject(result).done) { + state.innerIterator = state.innerNext = null; + outerLoop(); + } else resolve({ done: false, value: result.value }); + } catch (error1) { reject(error1); } + }, reject); + } catch (error) { reject(error); } + } else outerLoop(); + }; + + innerLoop(); + }); +}); + +$({ target: 'AsyncIterator', proto: true, real: true }, { + flatMap: function flatMap(mapper) { + return new AsyncIteratorProxy({ + iterator: anObject(this), + mapper: aFunction(mapper), + innerIterator: null, + innerNext: null + }); + } +}); diff --git a/core-js/modules/esnext.async-iterator.for-each.js b/core-js/modules/esnext.async-iterator.for-each.js new file mode 100644 index 0000000000..8dfbcedce9 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.for-each.js @@ -0,0 +1,10 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var $forEach = require('../internals/async-iterator-iteration').forEach; + +$({ target: 'AsyncIterator', proto: true, real: true }, { + forEach: function forEach(fn) { + return $forEach(this, fn); + } +}); diff --git a/core-js/modules/esnext.async-iterator.from.js b/core-js/modules/esnext.async-iterator.from.js new file mode 100644 index 0000000000..025b3e758b --- /dev/null +++ b/core-js/modules/esnext.async-iterator.from.js @@ -0,0 +1,30 @@ +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var path = require('../internals/path'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var toObject = require('../internals/to-object'); +var createAsyncIteratorProxy = require('../internals/create-async-iterator-proxy'); +var getAsyncIteratorMethod = require('../internals/get-async-iterator-method'); + +var AsyncIterator = path.AsyncIterator; + +var AsyncIteratorProxy = createAsyncIteratorProxy(function (arg) { + return anObject(this.next.call(this.iterator, arg)); +}, true); + +$({ target: 'AsyncIterator', stat: true }, { + from: function from(O) { + var object = toObject(O); + var usingIterator = getAsyncIteratorMethod(object); + var iterator; + if (usingIterator != null) { + iterator = aFunction(usingIterator).call(object); + if (iterator instanceof AsyncIterator) return iterator; + } else { + iterator = object; + } return new AsyncIteratorProxy({ + iterator: iterator + }); + } +}); diff --git a/core-js/modules/esnext.async-iterator.map.js b/core-js/modules/esnext.async-iterator.map.js new file mode 100644 index 0000000000..d0688dd4a6 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.map.js @@ -0,0 +1,30 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var createAsyncIteratorProxy = require('../internals/create-async-iterator-proxy'); + +var AsyncIteratorProxy = createAsyncIteratorProxy(function (arg, Promise) { + var state = this; + var mapper = state.mapper; + + return Promise.resolve(anObject(state.next.call(state.iterator, arg))).then(function (step) { + if (anObject(step).done) { + state.done = true; + return { done: true, value: undefined }; + } + return Promise.resolve(mapper(step.value)).then(function (value) { + return { done: false, value: value }; + }); + }); +}); + +$({ target: 'AsyncIterator', proto: true, real: true }, { + map: function map(mapper) { + return new AsyncIteratorProxy({ + iterator: anObject(this), + mapper: aFunction(mapper) + }); + } +}); diff --git a/core-js/modules/esnext.async-iterator.reduce.js b/core-js/modules/esnext.async-iterator.reduce.js new file mode 100644 index 0000000000..e5f7f9a3b4 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.reduce.js @@ -0,0 +1,46 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var getBuiltIn = require('../internals/get-built-in'); + +var Promise = getBuiltIn('Promise'); + +$({ target: 'AsyncIterator', proto: true, real: true }, { + reduce: function reduce(reducer /* , initialValue */) { + var iterator = anObject(this); + var next = aFunction(iterator.next); + var noInitial = arguments.length < 2; + var accumulator = noInitial ? undefined : arguments[1]; + aFunction(reducer); + + return new Promise(function (resolve, reject) { + var loop = function () { + try { + Promise.resolve(anObject(next.call(iterator))).then(function (step) { + try { + if (anObject(step).done) { + noInitial ? reject(TypeError('Reduce of empty iterator with no initial value')) : resolve(accumulator); + } else { + var value = step.value; + if (noInitial) { + noInitial = false; + accumulator = value; + loop(); + } else { + Promise.resolve(reducer(accumulator, value)).then(function (result) { + accumulator = result; + loop(); + }, reject); + } + } + } catch (err) { reject(err); } + }, reject); + } catch (error) { reject(error); } + }; + + loop(); + }); + } +}); diff --git a/core-js/modules/esnext.async-iterator.some.js b/core-js/modules/esnext.async-iterator.some.js new file mode 100644 index 0000000000..13fc1ee5af --- /dev/null +++ b/core-js/modules/esnext.async-iterator.some.js @@ -0,0 +1,10 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var $some = require('../internals/async-iterator-iteration').some; + +$({ target: 'AsyncIterator', proto: true, real: true }, { + some: function some(fn) { + return $some(this, fn); + } +}); diff --git a/core-js/modules/esnext.async-iterator.take.js b/core-js/modules/esnext.async-iterator.take.js new file mode 100644 index 0000000000..396a091af4 --- /dev/null +++ b/core-js/modules/esnext.async-iterator.take.js @@ -0,0 +1,22 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var anObject = require('../internals/an-object'); +var toPositiveInteger = require('../internals/to-positive-integer'); +var createAsyncIteratorProxy = require('../internals/create-async-iterator-proxy'); + +var AsyncIteratorProxy = createAsyncIteratorProxy(function (arg) { + if (!this.remaining--) { + this.done = true; + return { done: true, value: undefined }; + } return this.next.call(this.iterator, arg); +}); + +$({ target: 'AsyncIterator', proto: true, real: true }, { + take: function take(limit) { + return new AsyncIteratorProxy({ + iterator: anObject(this), + remaining: toPositiveInteger(limit) + }); + } +}); diff --git a/core-js/modules/esnext.async-iterator.to-array.js b/core-js/modules/esnext.async-iterator.to-array.js new file mode 100644 index 0000000000..04e3e7f8ee --- /dev/null +++ b/core-js/modules/esnext.async-iterator.to-array.js @@ -0,0 +1,10 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var $toArray = require('../internals/async-iterator-iteration').toArray; + +$({ target: 'AsyncIterator', proto: true, real: true }, { + toArray: function toArray() { + return $toArray(this); + } +}); diff --git a/core-js/modules/esnext.global-this.js b/core-js/modules/esnext.global-this.js index 7e925c8753..c62786d48e 100644 --- a/core-js/modules/esnext.global-this.js +++ b/core-js/modules/esnext.global-this.js @@ -1,8 +1,2 @@ -var $ = require('../internals/export'); -var global = require('../internals/global'); - -// `globalThis` object -// https://github.com/tc39/proposal-global -$({ global: true }, { - globalThis: global -}); +// TODO: Remove from `core-js@4` +require('./es.global-this'); diff --git a/core-js/modules/esnext.iterator.as-indexed-pairs.js b/core-js/modules/esnext.iterator.as-indexed-pairs.js new file mode 100644 index 0000000000..a803d43238 --- /dev/null +++ b/core-js/modules/esnext.iterator.as-indexed-pairs.js @@ -0,0 +1,20 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var anObject = require('../internals/an-object'); +var createIteratorProxy = require('../internals/create-iterator-proxy'); + +var IteratorProxy = createIteratorProxy(function (arg) { + var result = anObject(this.next.call(this.iterator, arg)); + var done = this.done = !!result.done; + if (!done) return [this.index++, result.value]; +}); + +$({ target: 'Iterator', proto: true, real: true }, { + asIndexedPairs: function asIndexedPairs() { + return new IteratorProxy({ + iterator: anObject(this), + index: 0 + }); + } +}); diff --git a/core-js/modules/esnext.iterator.constructor.js b/core-js/modules/esnext.iterator.constructor.js new file mode 100644 index 0000000000..1e971b66ee --- /dev/null +++ b/core-js/modules/esnext.iterator.constructor.js @@ -0,0 +1,43 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var global = require('../internals/global'); +var anInstance = require('../internals/an-instance'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var has = require('../internals/has'); +var wellKnownSymbol = require('../internals/well-known-symbol'); +var IteratorPrototype = require('../internals/iterators-core').IteratorPrototype; +var IS_PURE = require('../internals/is-pure'); + +var ITERATOR = wellKnownSymbol('iterator'); +var TO_STRING_TAG = wellKnownSymbol('toStringTag'); + +var NativeIterator = global.Iterator; + +// FF56- have non-standard global helper `Iterator` +var FORCED = IS_PURE || typeof NativeIterator != 'function' || NativeIterator.prototype !== IteratorPrototype; + +var IteratorConstructor = function Iterator() { + anInstance(this, IteratorConstructor); +}; + +if (IS_PURE) { + IteratorPrototype = {}; + createNonEnumerableProperty(IteratorPrototype, ITERATOR, function () { + return this; + }); +} + +if (!has(IteratorPrototype, TO_STRING_TAG)) { + createNonEnumerableProperty(IteratorPrototype, TO_STRING_TAG, 'Iterator'); +} + +if (!has(IteratorPrototype, 'constructor') || IteratorPrototype.constructor === Object) { + createNonEnumerableProperty(IteratorPrototype, 'constructor', IteratorConstructor); +} + +IteratorConstructor.prototype = IteratorPrototype; + +$({ global: true, forced: FORCED }, { + Iterator: IteratorConstructor +}); diff --git a/core-js/modules/esnext.iterator.drop.js b/core-js/modules/esnext.iterator.drop.js new file mode 100644 index 0000000000..5cf33915b7 --- /dev/null +++ b/core-js/modules/esnext.iterator.drop.js @@ -0,0 +1,30 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var anObject = require('../internals/an-object'); +var toPositiveInteger = require('../internals/to-positive-integer'); +var createIteratorProxy = require('../internals/create-iterator-proxy'); + +var IteratorProxy = createIteratorProxy(function (arg) { + var iterator = this.iterator; + var next = this.next; + var result, done; + while (this.remaining) { + this.remaining--; + result = anObject(next.call(iterator)); + done = this.done = !!result.done; + if (done) return; + } + result = anObject(next.call(iterator, arg)); + done = this.done = !!result.done; + if (!done) return result.value; +}); + +$({ target: 'Iterator', proto: true, real: true }, { + drop: function drop(limit) { + return new IteratorProxy({ + iterator: anObject(this), + remaining: toPositiveInteger(limit) + }); + } +}); diff --git a/core-js/modules/esnext.iterator.every.js b/core-js/modules/esnext.iterator.every.js new file mode 100644 index 0000000000..faa04fe9c0 --- /dev/null +++ b/core-js/modules/esnext.iterator.every.js @@ -0,0 +1,16 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var iterate = require('../internals/iterate'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); + +$({ target: 'Iterator', proto: true, real: true }, { + every: function every(fn) { + anObject(this); + aFunction(fn); + return !iterate(this, function (value) { + if (!fn(value)) return iterate.stop(); + }, undefined, false, true).stopped; + } +}); diff --git a/core-js/modules/esnext.iterator.filter.js b/core-js/modules/esnext.iterator.filter.js new file mode 100644 index 0000000000..2a779523b5 --- /dev/null +++ b/core-js/modules/esnext.iterator.filter.js @@ -0,0 +1,30 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var createIteratorProxy = require('../internals/create-iterator-proxy'); +var callWithSafeIterationClosing = require('../internals/call-with-safe-iteration-closing'); + +var IteratorProxy = createIteratorProxy(function (arg) { + var iterator = this.iterator; + var filterer = this.filterer; + var next = this.next; + var result, done, value; + while (true) { + result = anObject(next.call(iterator, arg)); + done = this.done = !!result.done; + if (done) return; + value = result.value; + if (callWithSafeIterationClosing(iterator, filterer, value)) return value; + } +}); + +$({ target: 'Iterator', proto: true, real: true }, { + filter: function filter(filterer) { + return new IteratorProxy({ + iterator: anObject(this), + filterer: aFunction(filterer) + }); + } +}); diff --git a/core-js/modules/esnext.iterator.find.js b/core-js/modules/esnext.iterator.find.js new file mode 100644 index 0000000000..42db9eacdc --- /dev/null +++ b/core-js/modules/esnext.iterator.find.js @@ -0,0 +1,16 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var iterate = require('../internals/iterate'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); + +$({ target: 'Iterator', proto: true, real: true }, { + find: function find(fn) { + anObject(this); + aFunction(fn); + return iterate(this, function (value) { + if (fn(value)) return iterate.stop(value); + }, undefined, false, true).result; + } +}); diff --git a/core-js/modules/esnext.iterator.flat-map.js b/core-js/modules/esnext.iterator.flat-map.js new file mode 100644 index 0000000000..404bb649fb --- /dev/null +++ b/core-js/modules/esnext.iterator.flat-map.js @@ -0,0 +1,45 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var isObject = require('../internals/is-object'); +var getIteratorMethod = require('../internals/get-iterator-method'); +var createIteratorProxy = require('../internals/create-iterator-proxy'); +var callWithSafeIterationClosing = require('../internals/call-with-safe-iteration-closing'); + +var IteratorProxy = createIteratorProxy(function (arg) { + var iterator = this.iterator; + var result, mapped, iteratorMethod, innerIterator; + + while (true) { + if (innerIterator = this.innerIterator) { + result = anObject(this.innerNext.call(innerIterator)); + if (!result.done) return result.value; + this.innerIterator = this.innerNext = null; + } + + result = anObject(this.next.call(iterator, arg)); + + if (this.done = !!result.done) return; + + mapped = callWithSafeIterationClosing(iterator, this.mapper, result.value); + + if (isObject(mapped) && (iteratorMethod = getIteratorMethod(mapped)) !== undefined) { + this.innerIterator = innerIterator = iteratorMethod.call(mapped); + this.innerNext = aFunction(innerIterator.next); + continue; + } return mapped; + } +}); + +$({ target: 'Iterator', proto: true, real: true }, { + flatMap: function flatMap(mapper) { + return new IteratorProxy({ + iterator: anObject(this), + mapper: aFunction(mapper), + innerIterator: null, + innerNext: null + }); + } +}); diff --git a/core-js/modules/esnext.iterator.for-each.js b/core-js/modules/esnext.iterator.for-each.js new file mode 100644 index 0000000000..c2427703ba --- /dev/null +++ b/core-js/modules/esnext.iterator.for-each.js @@ -0,0 +1,11 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var iterate = require('../internals/iterate'); +var anObject = require('../internals/an-object'); + +$({ target: 'Iterator', proto: true, real: true }, { + forEach: function forEach(fn) { + iterate(anObject(this), fn, undefined, false, true); + } +}); diff --git a/core-js/modules/esnext.iterator.from.js b/core-js/modules/esnext.iterator.from.js new file mode 100644 index 0000000000..dc66d29962 --- /dev/null +++ b/core-js/modules/esnext.iterator.from.js @@ -0,0 +1,32 @@ +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var path = require('../internals/path'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var toObject = require('../internals/to-object'); +var createIteratorProxy = require('../internals/create-iterator-proxy'); +var getIteratorMethod = require('../internals/get-iterator-method'); + +var Iterator = path.Iterator; + +var IteratorProxy = createIteratorProxy(function (arg) { + var result = anObject(this.next.call(this.iterator, arg)); + var done = this.done = !!result.done; + if (!done) return result.value; +}, true); + +$({ target: 'Iterator', stat: true }, { + from: function from(O) { + var object = toObject(O); + var usingIterator = getIteratorMethod(object); + var iterator; + if (usingIterator != null) { + iterator = aFunction(usingIterator).call(object); + if (iterator instanceof Iterator) return iterator; + } else { + iterator = object; + } return new IteratorProxy({ + iterator: iterator + }); + } +}); diff --git a/core-js/modules/esnext.iterator.map.js b/core-js/modules/esnext.iterator.map.js new file mode 100644 index 0000000000..c1c0119f0d --- /dev/null +++ b/core-js/modules/esnext.iterator.map.js @@ -0,0 +1,23 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); +var createIteratorProxy = require('../internals/create-iterator-proxy'); +var callWithSafeIterationClosing = require('../internals/call-with-safe-iteration-closing'); + +var IteratorProxy = createIteratorProxy(function (arg) { + var iterator = this.iterator; + var result = anObject(this.next.call(iterator, arg)); + var done = this.done = !!result.done; + if (!done) return callWithSafeIterationClosing(iterator, this.mapper, result.value); +}); + +$({ target: 'Iterator', proto: true, real: true }, { + map: function map(mapper) { + return new IteratorProxy({ + iterator: anObject(this), + mapper: aFunction(mapper) + }); + } +}); diff --git a/core-js/modules/esnext.iterator.reduce.js b/core-js/modules/esnext.iterator.reduce.js new file mode 100644 index 0000000000..029269d536 --- /dev/null +++ b/core-js/modules/esnext.iterator.reduce.js @@ -0,0 +1,25 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var iterate = require('../internals/iterate'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); + +$({ target: 'Iterator', proto: true, real: true }, { + reduce: function reduce(reducer /* , initialValue */) { + anObject(this); + aFunction(reducer); + var noInitial = arguments.length < 2; + var accumulator = noInitial ? undefined : arguments[1]; + iterate(this, function (value) { + if (noInitial) { + noInitial = false; + accumulator = value; + } else { + accumulator = reducer(accumulator, value); + } + }, undefined, false, true); + if (noInitial) throw TypeError('Reduce of empty iterator with no initial value'); + return accumulator; + } +}); diff --git a/core-js/modules/esnext.iterator.some.js b/core-js/modules/esnext.iterator.some.js new file mode 100644 index 0000000000..e32ce13dc2 --- /dev/null +++ b/core-js/modules/esnext.iterator.some.js @@ -0,0 +1,16 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var iterate = require('../internals/iterate'); +var aFunction = require('../internals/a-function'); +var anObject = require('../internals/an-object'); + +$({ target: 'Iterator', proto: true, real: true }, { + some: function some(fn) { + anObject(this); + aFunction(fn); + return iterate(this, function (value) { + if (fn(value)) return iterate.stop(); + }, undefined, false, true).stopped; + } +}); diff --git a/core-js/modules/esnext.iterator.take.js b/core-js/modules/esnext.iterator.take.js new file mode 100644 index 0000000000..81a927d386 --- /dev/null +++ b/core-js/modules/esnext.iterator.take.js @@ -0,0 +1,25 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var anObject = require('../internals/an-object'); +var toPositiveInteger = require('../internals/to-positive-integer'); +var createIteratorProxy = require('../internals/create-iterator-proxy'); + +var IteratorProxy = createIteratorProxy(function (arg) { + if (!this.remaining--) { + this.done = true; + return; + } + var result = anObject(this.next.call(this.iterator, arg)); + var done = this.done = !!result.done; + if (!done) return result.value; +}); + +$({ target: 'Iterator', proto: true, real: true }, { + take: function take(limit) { + return new IteratorProxy({ + iterator: anObject(this), + remaining: toPositiveInteger(limit) + }); + } +}); diff --git a/core-js/modules/esnext.iterator.to-array.js b/core-js/modules/esnext.iterator.to-array.js new file mode 100644 index 0000000000..90d32b8fcc --- /dev/null +++ b/core-js/modules/esnext.iterator.to-array.js @@ -0,0 +1,15 @@ +'use strict'; +// https://github.com/tc39/proposal-iterator-helpers +var $ = require('../internals/export'); +var iterate = require('../internals/iterate'); +var anObject = require('../internals/an-object'); + +var push = [].push; + +$({ target: 'Iterator', proto: true, real: true }, { + toArray: function toArray() { + var result = []; + iterate(anObject(this), push, result, false, true); + return result; + } +}); diff --git a/core-js/modules/esnext.map.reduce.js b/core-js/modules/esnext.map.reduce.js index 91480f4873..43153f8e36 100644 --- a/core-js/modules/esnext.map.reduce.js +++ b/core-js/modules/esnext.map.reduce.js @@ -12,17 +12,18 @@ $({ target: 'Map', proto: true, real: true, forced: IS_PURE }, { reduce: function reduce(callbackfn /* , initialValue */) { var map = anObject(this); var iterator = getMapIterator(map); - var accumulator, step; + var noInitial = arguments.length < 2; + var accumulator = noInitial ? undefined : arguments[1]; aFunction(callbackfn); - if (arguments.length > 1) accumulator = arguments[1]; - else { - step = iterator.next(); - if (step.done) throw TypeError('Reduce of empty map with no initial value'); - accumulator = step.value[1]; - } iterate(iterator, function (key, value) { - accumulator = callbackfn(accumulator, value, key, map); + if (noInitial) { + noInitial = false; + accumulator = value; + } else { + accumulator = callbackfn(accumulator, value, key, map); + } }, undefined, true, true); + if (noInitial) throw TypeError('Reduce of empty map with no initial value'); return accumulator; } }); diff --git a/core-js/modules/esnext.map.update-or-insert.js b/core-js/modules/esnext.map.update-or-insert.js index 6a0dac88fe..f429b21344 100644 --- a/core-js/modules/esnext.map.update-or-insert.js +++ b/core-js/modules/esnext.map.update-or-insert.js @@ -1,18 +1,11 @@ 'use strict'; +// TODO: remove from `core-js@4` var $ = require('../internals/export'); var IS_PURE = require('../internals/is-pure'); -var anObject = require('../internals/an-object'); -var aFunction = require('../internals/a-function'); +var $upsert = require('../internals/map-upsert'); -// `Set.prototype.updateOrInsert` method -// https://docs.google.com/presentation/d/1_xtrGSoN1-l2Q74eCXPHBbbrBHsVyqArWN0ebnW-pVQ/ +// `Map.prototype.updateOrInsert` method (replaced by `Map.prototype.upsert`) +// https://github.com/thumbsupep/proposal-upsert $({ target: 'Map', proto: true, real: true, forced: IS_PURE }, { - updateOrInsert: function updateOrInsert(key, onUpdate, onInsert) { - var map = anObject(this); - aFunction(onUpdate); - aFunction(onInsert); - var value = map.has(key) ? onUpdate(map.get(key)) : onInsert(); - map.set(key, value); - return value; - } + updateOrInsert: $upsert }); diff --git a/core-js/modules/esnext.map.upsert.js b/core-js/modules/esnext.map.upsert.js new file mode 100644 index 0000000000..a30e8351c5 --- /dev/null +++ b/core-js/modules/esnext.map.upsert.js @@ -0,0 +1,10 @@ +'use strict'; +var $ = require('../internals/export'); +var IS_PURE = require('../internals/is-pure'); +var $upsert = require('../internals/map-upsert'); + +// `Map.prototype.upsert` method +// https://github.com/thumbsupep/proposal-upsert +$({ target: 'Map', proto: true, real: true, forced: IS_PURE }, { + upsert: $upsert +}); diff --git a/core-js/modules/esnext.math.signbit.js b/core-js/modules/esnext.math.signbit.js index 1631c9b773..0d8f4e82bc 100644 --- a/core-js/modules/esnext.math.signbit.js +++ b/core-js/modules/esnext.math.signbit.js @@ -4,7 +4,6 @@ var $ = require('../internals/export'); // https://github.com/tc39/proposal-Math.signbit $({ target: 'Math', stat: true }, { signbit: function signbit(x) { - // eslint-disable-next-line no-self-compare return (x = +x) != x ? x : x == 0 ? 1 / x == Infinity : x > 0; } }); diff --git a/core-js/modules/esnext.observable.js b/core-js/modules/esnext.observable.js index fdfda49c6b..4eaab232cb 100644 --- a/core-js/modules/esnext.observable.js +++ b/core-js/modules/esnext.observable.js @@ -8,7 +8,7 @@ var anObject = require('../internals/an-object'); var isObject = require('../internals/is-object'); var anInstance = require('../internals/an-instance'); var defineProperty = require('../internals/object-define-property').f; -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var redefineAll = require('../internals/redefine-all'); var getIterator = require('../internals/get-iterator'); var iterate = require('../internals/iterate'); @@ -198,7 +198,7 @@ redefineAll($Observable, { } }); -hide($Observable.prototype, OBSERVABLE, function () { return this; }); +createNonEnumerableProperty($Observable.prototype, OBSERVABLE, function () { return this; }); $({ global: true }, { Observable: $Observable diff --git a/core-js/modules/esnext.set.reduce.js b/core-js/modules/esnext.set.reduce.js index 6a08fa23d8..00e30de28b 100644 --- a/core-js/modules/esnext.set.reduce.js +++ b/core-js/modules/esnext.set.reduce.js @@ -12,17 +12,18 @@ $({ target: 'Set', proto: true, real: true, forced: IS_PURE }, { reduce: function reduce(callbackfn /* , initialValue */) { var set = anObject(this); var iterator = getSetIterator(set); - var accumulator, step; + var noInitial = arguments.length < 2; + var accumulator = noInitial ? undefined : arguments[1]; aFunction(callbackfn); - if (arguments.length > 1) accumulator = arguments[1]; - else { - step = iterator.next(); - if (step.done) throw TypeError('Reduce of empty set with no initial value'); - accumulator = step.value; - } iterate(iterator, function (value) { - accumulator = callbackfn(accumulator, value, value, set); + if (noInitial) { + noInitial = false; + accumulator = value; + } else { + accumulator = callbackfn(accumulator, value, value, set); + } }, undefined, false, true); + if (noInitial) throw TypeError('Reduce of empty set with no initial value'); return accumulator; } }); diff --git a/core-js/modules/esnext.string.match-all.js b/core-js/modules/esnext.string.match-all.js index d507e03fbf..b01996dc94 100644 --- a/core-js/modules/esnext.string.match-all.js +++ b/core-js/modules/esnext.string.match-all.js @@ -1,3 +1,2 @@ // TODO: Remove from `core-js@4` -require('./es.symbol.match-all'); require('./es.string.match-all'); diff --git a/core-js/modules/esnext.string.replace-all.js b/core-js/modules/esnext.string.replace-all.js index 521d7de144..40ea5e54f2 100644 --- a/core-js/modules/esnext.string.replace-all.js +++ b/core-js/modules/esnext.string.replace-all.js @@ -1,42 +1,39 @@ 'use strict'; var $ = require('../internals/export'); -var hide = require('../internals/hide'); var requireObjectCoercible = require('../internals/require-object-coercible'); -var anObject = require('../internals/an-object'); var isRegExp = require('../internals/is-regexp'); var getRegExpFlags = require('../internals/regexp-flags'); -var speciesConstructor = require('../internals/species-constructor'); var wellKnownSymbol = require('../internals/well-known-symbol'); var IS_PURE = require('../internals/is-pure'); -var REPLACE_ALL = wellKnownSymbol('replaceAll'); +var REPLACE = wellKnownSymbol('replace'); var RegExpPrototype = RegExp.prototype; -var $replaceAll = function (string, replaceValue) { - var rx = anObject(this); - var flags = String('flags' in RegExpPrototype ? rx.flags : getRegExpFlags.call(rx)); - if (!~flags.indexOf('g')) { - rx = new (speciesConstructor(rx, RegExp))(rx.source, flags + 'g'); - } - return String(string).replace(rx, replaceValue); -}; - // `String.prototype.replaceAll` method // https://github.com/tc39/proposal-string-replace-all $({ target: 'String', proto: true }, { replaceAll: function replaceAll(searchValue, replaceValue) { var O = requireObjectCoercible(this); - var replacer, string, searchString, template, result, index; + var IS_REG_EXP, flags, replacer, string, searchString, template, result, index; if (searchValue != null) { - replacer = searchValue[REPLACE_ALL]; + IS_REG_EXP = isRegExp(searchValue); + if (IS_REG_EXP) { + flags = String(requireObjectCoercible('flags' in RegExpPrototype + ? searchValue.flags + : getRegExpFlags.call(searchValue) + )); + if (!~flags.indexOf('g')) throw TypeError('`.replaceAll` does not allow non-global regexes'); + } + replacer = searchValue[REPLACE]; if (replacer !== undefined) { return replacer.call(searchValue, O, replaceValue); - } else if (IS_PURE && isRegExp(searchValue)) { - return $replaceAll.call(searchValue, O, replaceValue); + } else if (IS_PURE && IS_REG_EXP) { + return String(O).replace(searchValue, replaceValue); } } string = String(O); searchString = String(searchValue); + if (searchString === '') return replaceAll.call(string, /(?:)/g, replaceValue); template = string.split(searchString); if (typeof replaceValue !== 'function') { return template.join(String(replaceValue)); @@ -49,5 +46,3 @@ $({ target: 'String', proto: true }, { return result; } }); - -IS_PURE || REPLACE_ALL in RegExpPrototype || hide(RegExpPrototype, REPLACE_ALL, $replaceAll); diff --git a/core-js/modules/esnext.symbol.replace-all.js b/core-js/modules/esnext.symbol.replace-all.js index c103887233..82cbd293d9 100644 --- a/core-js/modules/esnext.symbol.replace-all.js +++ b/core-js/modules/esnext.symbol.replace-all.js @@ -1,5 +1,4 @@ +// TODO: remove from `core-js@4` var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); -// `Symbol.replaceAll` well-known symbol -// https://tc39.github.io/proposal-string-replaceall/ defineWellKnownSymbol('replaceAll'); diff --git a/core-js/modules/esnext.weak-map.upsert.js b/core-js/modules/esnext.weak-map.upsert.js new file mode 100644 index 0000000000..1e0d60c971 --- /dev/null +++ b/core-js/modules/esnext.weak-map.upsert.js @@ -0,0 +1,10 @@ +'use strict'; +var $ = require('../internals/export'); +var IS_PURE = require('../internals/is-pure'); +var $upsert = require('../internals/map-upsert'); + +// `WeakMap.prototype.upsert` method +// https://github.com/thumbsupep/proposal-upsert +$({ target: 'WeakMap', proto: true, real: true, forced: IS_PURE }, { + upsert: $upsert +}); diff --git a/core-js/modules/web.dom-collections.for-each.js b/core-js/modules/web.dom-collections.for-each.js index 72c125aae4..941a583a1b 100644 --- a/core-js/modules/web.dom-collections.for-each.js +++ b/core-js/modules/web.dom-collections.for-each.js @@ -1,14 +1,14 @@ var global = require('../internals/global'); var DOMIterables = require('../internals/dom-iterables'); var forEach = require('../internals/array-for-each'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); for (var COLLECTION_NAME in DOMIterables) { var Collection = global[COLLECTION_NAME]; var CollectionPrototype = Collection && Collection.prototype; // some Chrome versions have non-configurable methods on DOMTokenList if (CollectionPrototype && CollectionPrototype.forEach !== forEach) try { - hide(CollectionPrototype, 'forEach', forEach); + createNonEnumerableProperty(CollectionPrototype, 'forEach', forEach); } catch (error) { CollectionPrototype.forEach = forEach; } diff --git a/core-js/modules/web.dom-collections.iterator.js b/core-js/modules/web.dom-collections.iterator.js index 38a7709193..b2ecd17905 100644 --- a/core-js/modules/web.dom-collections.iterator.js +++ b/core-js/modules/web.dom-collections.iterator.js @@ -1,7 +1,7 @@ var global = require('../internals/global'); var DOMIterables = require('../internals/dom-iterables'); var ArrayIteratorMethods = require('../modules/es.array.iterator'); -var hide = require('../internals/hide'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var wellKnownSymbol = require('../internals/well-known-symbol'); var ITERATOR = wellKnownSymbol('iterator'); @@ -14,15 +14,17 @@ for (var COLLECTION_NAME in DOMIterables) { if (CollectionPrototype) { // some Chrome versions have non-configurable methods on DOMTokenList if (CollectionPrototype[ITERATOR] !== ArrayValues) try { - hide(CollectionPrototype, ITERATOR, ArrayValues); + createNonEnumerableProperty(CollectionPrototype, ITERATOR, ArrayValues); } catch (error) { CollectionPrototype[ITERATOR] = ArrayValues; } - if (!CollectionPrototype[TO_STRING_TAG]) hide(CollectionPrototype, TO_STRING_TAG, COLLECTION_NAME); + if (!CollectionPrototype[TO_STRING_TAG]) { + createNonEnumerableProperty(CollectionPrototype, TO_STRING_TAG, COLLECTION_NAME); + } if (DOMIterables[COLLECTION_NAME]) for (var METHOD_NAME in ArrayIteratorMethods) { // some Chrome versions have non-configurable methods on DOMTokenList if (CollectionPrototype[METHOD_NAME] !== ArrayIteratorMethods[METHOD_NAME]) try { - hide(CollectionPrototype, METHOD_NAME, ArrayIteratorMethods[METHOD_NAME]); + createNonEnumerableProperty(CollectionPrototype, METHOD_NAME, ArrayIteratorMethods[METHOD_NAME]); } catch (error) { CollectionPrototype[METHOD_NAME] = ArrayIteratorMethods[METHOD_NAME]; } diff --git a/core-js/modules/web.url-search-params.js b/core-js/modules/web.url-search-params.js index 57454b3276..e09b6fc036 100644 --- a/core-js/modules/web.url-search-params.js +++ b/core-js/modules/web.url-search-params.js @@ -2,6 +2,7 @@ // TODO: in core-js@4, move /modules/ dependencies to public entries for better optimization by tools like `preset-env` require('../modules/es.array.iterator'); var $ = require('../internals/export'); +var getBuiltIn = require('../internals/get-built-in'); var USE_NATIVE_URL = require('../internals/native-url'); var redefine = require('../internals/redefine'); var redefineAll = require('../internals/redefine-all'); @@ -11,12 +12,17 @@ var InternalStateModule = require('../internals/internal-state'); var anInstance = require('../internals/an-instance'); var hasOwn = require('../internals/has'); var bind = require('../internals/bind-context'); +var classof = require('../internals/classof'); var anObject = require('../internals/an-object'); var isObject = require('../internals/is-object'); +var create = require('../internals/object-create'); +var createPropertyDescriptor = require('../internals/create-property-descriptor'); var getIterator = require('../internals/get-iterator'); var getIteratorMethod = require('../internals/get-iterator-method'); var wellKnownSymbol = require('../internals/well-known-symbol'); +var $fetch = getBuiltIn('fetch'); +var Headers = getBuiltIn('Headers'); var ITERATOR = wellKnownSymbol('iterator'); var URL_SEARCH_PARAMS = 'URLSearchParams'; var URL_SEARCH_PARAMS_ITERATOR = URL_SEARCH_PARAMS + 'Iterator'; @@ -121,7 +127,7 @@ var URLSearchParamsConstructor = function URLSearchParams(/* init */) { var init = arguments.length > 0 ? arguments[0] : undefined; var that = this; var entries = []; - var iteratorMethod, iterator, step, entryIterator, first, second, key; + var iteratorMethod, iterator, next, step, entryIterator, entryNext, first, second, key; setInternalState(that, { type: URL_SEARCH_PARAMS, @@ -135,12 +141,14 @@ var URLSearchParamsConstructor = function URLSearchParams(/* init */) { iteratorMethod = getIteratorMethod(init); if (typeof iteratorMethod === 'function') { iterator = iteratorMethod.call(init); - while (!(step = iterator.next()).done) { + next = iterator.next; + while (!(step = next.call(iterator)).done) { entryIterator = getIterator(anObject(step.value)); + entryNext = entryIterator.next; if ( - (first = entryIterator.next()).done || - (second = entryIterator.next()).done || - !entryIterator.next().done + (first = entryNext.call(entryIterator)).done || + (second = entryNext.call(entryIterator)).done || + !entryNext.call(entryIterator).done ) throw TypeError('Expected sequence with length 2'); entries.push({ key: first.value + '', value: second.value + '' }); } @@ -305,6 +313,34 @@ $({ global: true, forced: !USE_NATIVE_URL }, { URLSearchParams: URLSearchParamsConstructor }); +// Wrap `fetch` for correct work with polyfilled `URLSearchParams` +// https://github.com/zloirock/core-js/issues/674 +if (!USE_NATIVE_URL && typeof $fetch == 'function' && typeof Headers == 'function') { + $({ global: true, enumerable: true, forced: true }, { + fetch: function fetch(input /* , init */) { + var args = [input]; + var init, body, headers; + if (arguments.length > 1) { + init = arguments[1]; + if (isObject(init)) { + body = init.body; + if (classof(body) === URL_SEARCH_PARAMS) { + headers = new Headers(init.headers); + if (!headers.has('content-type')) { + headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); + } + init = create(init, { + body: createPropertyDescriptor(0, String(body)), + headers: createPropertyDescriptor(0, headers) + }); + } + } + args.push(init); + } return $fetch.apply(this, args); + } + }); +} + module.exports = { URLSearchParams: URLSearchParamsConstructor, getState: getInternalParamsState diff --git a/core-js/modules/web.url.js b/core-js/modules/web.url.js index 2d4a924985..6f29c0856a 100644 --- a/core-js/modules/web.url.js +++ b/core-js/modules/web.url.js @@ -260,7 +260,6 @@ var percentEncode = function (char, set) { var specialSchemes = { ftp: 21, file: null, - gopher: 70, http: 80, https: 443, ws: 80, diff --git a/core-js/package.json b/core-js/package.json index b6207c31b7..58f337c943 100644 --- a/core-js/package.json +++ b/core-js/package.json @@ -1,7 +1,7 @@ { "name": "core-js", "description": "Standard library", - "version": "3.2.1", + "version": "3.3.5", "repository": { "type": "git", "url": "https://github.com/zloirock/core-js.git" @@ -18,6 +18,7 @@ "ES2017", "ES2018", "ES2019", + "ES2020", "ECMAScript 3", "ECMAScript 5", "ECMAScript 6", @@ -27,6 +28,7 @@ "ECMAScript 2017", "ECMAScript 2018", "ECMAScript 2019", + "ECMAScript 2020", "Harmony", "Strawman", "Map", @@ -46,6 +48,6 @@ "shim" ], "scripts": { - "postinstall": "node scripts/postinstall || echo \"ignore\"" + "postinstall": "node postinstall || echo \"ignore\"" } } diff --git a/core-js/postinstall.js b/core-js/postinstall.js new file mode 100644 index 0000000000..d13423937c --- /dev/null +++ b/core-js/postinstall.js @@ -0,0 +1,49 @@ +/* eslint-disable max-len */ +var fs = require('fs'); +var os = require('os'); +var path = require('path'); +var env = process.env; + +var ADBLOCK = is(env.ADBLOCK); +var CI = is(env.CI); +var COLOR = is(env.npm_config_color); +var DISABLE_OPENCOLLECTIVE = is(env.DISABLE_OPENCOLLECTIVE); +var SILENT = ['silent', 'error', 'warn'].indexOf(env.npm_config_loglevel) !== -1; +var MINUTE = 60 * 1000; + +var BANNER = '\u001B[96mThank you for using core-js (\u001B[94m https://github.com/zloirock/core-js \u001B[96m) for polyfilling JavaScript standard library!\u001B[0m\n\n' + + '\u001B[96mThe project needs your help! Please consider supporting of core-js on Open Collective or Patreon: \u001B[0m\n' + + '\u001B[96m>\u001B[94m https://opencollective.com/core-js \u001B[0m\n' + + '\u001B[96m>\u001B[94m https://www.patreon.com/zloirock \u001B[0m\n\n' + + '\u001B[96mAlso, the author of core-js (\u001B[94m https://github.com/zloirock \u001B[96m) is looking for a good job -)\u001B[0m\n'; + +function is(it) { + return !!it && it !== '0' && it !== 'false'; +} + +function isBannerRequired() { + if (ADBLOCK || CI || DISABLE_OPENCOLLECTIVE || SILENT) return false; + var file = path.join(os.tmpdir(), 'core-js-banners'); + var banners = []; + try { + var DELTA = Date.now() - fs.statSync(file).mtime; + if (DELTA >= 0 && DELTA < MINUTE * 3) { + banners = JSON.parse(fs.readFileSync(file, 'utf8')); + if (banners.indexOf(BANNER) !== -1) return false; + } + } catch (error) { + banners = []; + } + try { + banners.push(BANNER); + fs.writeFileSync(file, JSON.stringify(banners), 'utf8'); + } catch (error) { /* empty */ } + return true; +} + +function showBanner() { + // eslint-disable-next-line no-console,no-control-regex + console.log(COLOR ? BANNER : BANNER.replace(/\u001B\[\d+m/g, '')); +} + +if (isBannerRequired()) showBanner(); diff --git a/core-js/proposals/iterator-helpers.js b/core-js/proposals/iterator-helpers.js new file mode 100644 index 0000000000..8b81aeaed1 --- /dev/null +++ b/core-js/proposals/iterator-helpers.js @@ -0,0 +1,20 @@ +require('../modules/esnext.async-iterator.constructor'); +require('../modules/esnext.async-iterator.drop'); +require('../modules/esnext.async-iterator.filter'); +require('../modules/esnext.async-iterator.from'); +require('../modules/esnext.async-iterator.map'); +require('../modules/esnext.async-iterator.take'); +require('../modules/esnext.async-iterator.to-array'); +require('../modules/esnext.iterator.constructor'); +require('../modules/esnext.iterator.as-indexed-pairs'); +require('../modules/esnext.iterator.drop'); +require('../modules/esnext.iterator.every'); +require('../modules/esnext.iterator.filter'); +require('../modules/esnext.iterator.find'); +require('../modules/esnext.iterator.for-each'); +require('../modules/esnext.iterator.from'); +require('../modules/esnext.iterator.map'); +require('../modules/esnext.iterator.reduce'); +require('../modules/esnext.iterator.some'); +require('../modules/esnext.iterator.take'); +require('../modules/esnext.iterator.to-array'); diff --git a/core-js/proposals/map-update-or-insert.js b/core-js/proposals/map-update-or-insert.js index 0438870dbd..28603018c8 100644 --- a/core-js/proposals/map-update-or-insert.js +++ b/core-js/proposals/map-update-or-insert.js @@ -1 +1,2 @@ -require('../modules/esnext.map.update-or-insert'); +// TODO: remove from `core-js@4` +require('./map-upsert'); diff --git a/core-js/proposals/map-upsert.js b/core-js/proposals/map-upsert.js new file mode 100644 index 0000000000..ee94275fac --- /dev/null +++ b/core-js/proposals/map-upsert.js @@ -0,0 +1,5 @@ +// https://github.com/thumbsupep/proposal-upsert +// TODO: remove from `core-js@4` +require('../modules/esnext.map.update-or-insert'); +require('../modules/esnext.map.upsert'); +require('../modules/esnext.weak-map.upsert'); diff --git a/core-js/scripts/postinstall.js b/core-js/scripts/postinstall.js deleted file mode 100644 index 4f3d3d7b0e..0000000000 --- a/core-js/scripts/postinstall.js +++ /dev/null @@ -1,24 +0,0 @@ -/* eslint-disable max-len */ -var env = process.env; -var ADBLOCK = is(env.ADBLOCK); -var CI = is(env.CI); -var COLOR = is(env.npm_config_color); -var DISABLE_OPENCOLLECTIVE = is(env.DISABLE_OPENCOLLECTIVE); -var SILENT = !!~['silent', 'error', 'warn'].indexOf(env.npm_config_loglevel); - -function is(it) { - return !!it && it !== '0' && it !== 'false'; -} - -function log(it) { - // eslint-disable-next-line no-console,no-control-regex - console.log(COLOR ? it : it.replace(/\u001B\[\d+m/g, '')); -} - -if (!ADBLOCK && !CI && !DISABLE_OPENCOLLECTIVE && !SILENT) { - log('\u001B[96mThank you for using core-js (\u001B[94m https://github.com/zloirock/core-js \u001B[96m) for polyfilling JavaScript standard library!\u001B[0m\n'); - log('\u001B[96mThe project needs your help! Please consider supporting of core-js on Open Collective or Patreon: \u001B[0m'); - log('\u001B[96m>\u001B[94m https://opencollective.com/core-js \u001B[0m'); - log('\u001B[96m>\u001B[94m https://www.patreon.com/zloirock \u001B[0m\n'); - log('\u001B[96mAlso, the author of core-js (\u001B[94m https://github.com/zloirock \u001B[96m) is looking for a good job -)\u001B[0m\n'); -} diff --git a/core-js/stable/global-this.js b/core-js/stable/global-this.js index b6c15b3930..bd15daee57 100644 --- a/core-js/stable/global-this.js +++ b/core-js/stable/global-this.js @@ -1,3 +1 @@ -require('../modules/esnext.global-this'); - -module.exports = require('../internals/path').globalThis; +module.exports = require('../es/global-this'); diff --git a/core-js/stage/1.js b/core-js/stage/1.js index 98b215ce52..20c25c7571 100644 --- a/core-js/stage/1.js +++ b/core-js/stage/1.js @@ -2,7 +2,6 @@ require('../proposals/array-last'); require('../proposals/collection-methods'); require('../proposals/collection-of-from'); require('../proposals/keys-composition'); -require('../proposals/map-update-or-insert'); require('../proposals/math-extensions'); require('../proposals/math-signbit'); require('../proposals/number-from-string'); diff --git a/core-js/stage/2.js b/core-js/stage/2.js index b2a8b4d2a5..3fdaf3a214 100644 --- a/core-js/stage/2.js +++ b/core-js/stage/2.js @@ -1,7 +1,7 @@ require('../proposals/array-is-template-object'); -require('../proposals/promise-any'); +require('../proposals/iterator-helpers'); +require('../proposals/map-upsert'); require('../proposals/set-methods'); -require('../proposals/string-replace-all'); require('../proposals/using-statement'); module.exports = require('./3'); diff --git a/core-js/stage/3.js b/core-js/stage/3.js index b90990f5c1..6df0b0cafe 100644 --- a/core-js/stage/3.js +++ b/core-js/stage/3.js @@ -1,3 +1,4 @@ -require('../proposals/global-this'); +require('../proposals/promise-any'); +require('../proposals/string-replace-all'); module.exports = require('./4'); diff --git a/core-js/stage/4.js b/core-js/stage/4.js index ecd7460487..031e65e307 100644 --- a/core-js/stage/4.js +++ b/core-js/stage/4.js @@ -1,3 +1,4 @@ +require('../proposals/global-this'); require('../proposals/promise-all-settled'); require('../proposals/string-match-all'); diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md index 9340d23967..79fb2ea50a 100644 --- a/doc/user/project/members/share_project_with_groups.md +++ b/doc/user/project/members/share_project_with_groups.md @@ -44,6 +44,10 @@ Admins are able to share projects with any group in the system. In the example above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Maintainer' or 'Owner') will only have 'Developer' access to 'Project Acme'. +## Sharing public project with private group + +When sharing a public project with a private group, owners and maintainers of the project will see the name of the group in the `members` page. Owners will also have the possibility to see members of the private group they don't have access to when mentioning them in the issue or merge request. + ## Share project with group lock It is possible to prevent projects in a group from [sharing diff --git a/lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb b/lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb new file mode 100644 index 0000000000..ccf9e59730 --- /dev/null +++ b/lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# Recursive queries, with relatively low effort, can quickly spiral out of control exponentially +# and may not be picked up by depth and complexity alone. +module Gitlab + module Graphql + module QueryAnalyzers + class RecursionAnalyzer + IGNORED_FIELDS = %w(node edges ofType).freeze + RECURSION_THRESHOLD = 2 + + def initial_value(query) + { + recurring_fields: {} + } + end + + def call(memo, visit_type, irep_node) + return memo if skip_node?(irep_node) + + node_name = irep_node.ast_node.name + times_encountered = memo[node_name] || 0 + + if visit_type == :enter + times_encountered += 1 + memo[:recurring_fields][node_name] = times_encountered if recursion_too_deep?(node_name, times_encountered) + else + times_encountered -= 1 + end + + memo[node_name] = times_encountered + memo + end + + def final_value(memo) + recurring_fields = memo[:recurring_fields] + recurring_fields = recurring_fields.select { |k, v| recursion_too_deep?(k, v) } + if recurring_fields.any? + GraphQL::AnalysisError.new("Recursive query - too many of fields '#{recurring_fields}' detected in single branch of the query") + end + end + + private + + def recursion_too_deep?(node_name, times_encountered) + return if IGNORED_FIELDS.include?(node_name) + + times_encountered > recursion_threshold + end + + def skip_node?(irep_node) + ast_node = irep_node.ast_node + !ast_node.is_a?(GraphQL::Language::Nodes::Field) || ast_node.selections.empty? + end + + def recursion_threshold + RECURSION_THRESHOLD + end + end + end + end +end diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb index bc467486ee..0dd6b8a809 100644 --- a/lib/gitlab/other_markup.rb +++ b/lib/gitlab/other_markup.rb @@ -10,7 +10,7 @@ module Gitlab def self.render(file_name, input, context) html = GitHub::Markup.render(file_name, input) .force_encoding(input.encoding) - context[:pipeline] = :markup + context[:pipeline] ||= :markup html = Banzai.render(html, context) diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index ce4c161168..cf4e85363f 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -162,7 +162,7 @@ module Gitlab return Milestone.none if project_ids.nil? authorized_project_ids_relation = - Project.where(id: project_ids).ids_with_milestone_available_for(current_user) + Project.where(id: project_ids).ids_with_issuables_available_for(current_user) milestones.where(project_id: authorized_project_ids_relation) end diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index 9c35d200dc..cba7d4dc19 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -11,11 +11,14 @@ module Gitlab # Validates the given url according to the constraints specified by arguments. # # ports - Raises error if the given URL port does is not between given ports. - # allow_localhost - Raises error if URL resolves to a localhost IP address and argument is true. - # allow_local_network - Raises error if URL resolves to a link-local address and argument is true. + # allow_localhost - Raises error if URL resolves to a localhost IP address and argument is false. + # allow_local_network - Raises error if URL resolves to a link-local address and argument is false. # ascii_only - Raises error if URL has unicode characters and argument is true. # enforce_user - Raises error if URL user doesn't start with alphanumeric characters and argument is true. # enforce_sanitization - Raises error if URL includes any HTML/CSS/JS tags and argument is true. + # require_absolute - Raises error if URL is not absolute and argument is true. + # Allow relative URLs beginning with slash when argument is false + # Raises error if relative URL does not begin with slash and argument is false # # Returns an array with [, ]. # rubocop:disable Metrics/ParameterLists @@ -28,7 +31,8 @@ module Gitlab ascii_only: false, enforce_user: false, enforce_sanitization: false, - dns_rebind_protection: true) + dns_rebind_protection: true, + require_absolute: true) # rubocop:enable Metrics/ParameterLists return [nil, nil] if url.nil? @@ -42,14 +46,15 @@ module Gitlab ports: ports, enforce_sanitization: enforce_sanitization, enforce_user: enforce_user, - ascii_only: ascii_only + ascii_only: ascii_only, + require_absolute: require_absolute ) normalized_hostname = uri.normalized_host hostname = uri.hostname port = get_port(uri) - address_info = get_address_info(hostname, port) + address_info = get_address_info(hostname, port) if require_absolute || uri.absolute? return [uri, nil] unless address_info ip_address = ip_address(address_info) @@ -98,12 +103,14 @@ module Gitlab address_info.first&.ip_address end - def validate_uri(uri:, schemes:, ports:, enforce_sanitization:, enforce_user:, ascii_only:) + def validate_uri(uri:, schemes:, ports:, enforce_sanitization:, enforce_user:, ascii_only:, require_absolute:) validate_html_tags(uri) if enforce_sanitization + validate_absolute(uri) if require_absolute + validate_relative(uri) unless require_absolute return if internal?(uri) - validate_scheme(uri.scheme, schemes) + validate_scheme(uri.scheme, schemes, require_absolute) validate_port(get_port(uri), ports) if ports.any? validate_user(uri.user) if enforce_user validate_hostname(uri.hostname) @@ -183,8 +190,20 @@ module Gitlab raise BlockedUrlError, "Only allowed ports are #{ports.join(', ')}, and any over 1024" end - def validate_scheme(scheme, schemes) - if scheme.blank? || (schemes.any? && !schemes.include?(scheme)) + def validate_absolute(uri) + return if uri.absolute? + + raise BlockedUrlError, 'must be absolute' + end + + def validate_relative(uri) + return if uri.absolute? || uri.path.starts_with?('/') + + raise BlockedUrlError, 'relative path must begin with a / (slash)' + end + + def validate_scheme(scheme, schemes, require_absolute) + if (require_absolute && scheme.blank?) || (schemes.any? && !schemes.include?(scheme)) raise BlockedUrlError, "Only allowed schemes are #{schemes.join(', ')}" end end @@ -243,9 +262,10 @@ module Gitlab end def internal_web?(uri) - uri.scheme == config.gitlab.protocol && - uri.hostname == config.gitlab.host && - (uri.port.blank? || uri.port == config.gitlab.port) + (uri.scheme.blank? && uri.hostname.blank? && uri.port.blank? && uri.path.starts_with?('/')) || + (uri.scheme == config.gitlab.protocol && + uri.hostname == config.gitlab.host && + (uri.port.blank? || uri.port == config.gitlab.port)) end def internal_shell?(uri) diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 0b3833e651..622122a755 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -176,12 +176,6 @@ describe ApplicationController do expect(controller).to receive(:not_found) controller.send(:route_not_found) end - - it 'does redirect to login page via authenticate_user! if not authenticated' do - allow(controller).to receive(:current_user).and_return(nil) - expect(controller).to receive(:authenticate_user!) - controller.send(:route_not_found) - end end describe '#set_page_title_header' do diff --git a/spec/controllers/concerns/internal_redirect_spec.rb b/spec/controllers/concerns/internal_redirect_spec.rb index da68c8c869..e5e50cfd55 100644 --- a/spec/controllers/concerns/internal_redirect_spec.rb +++ b/spec/controllers/concerns/internal_redirect_spec.rb @@ -19,7 +19,8 @@ describe InternalRedirect do [ 'Hello world', '//example.com/hello/world', - 'https://example.com/hello/world' + 'https://example.com/hello/world', + "not-starting-with-a-slash\n/starting/with/slash" ] end diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb index cb8c0b8f71..823b9a5043 100644 --- a/spec/controllers/concerns/lfs_request_spec.rb +++ b/spec/controllers/concerns/lfs_request_spec.rb @@ -16,13 +16,17 @@ describe LfsRequest do end def project - @project ||= Project.find(params[:id]) + @project ||= Project.find_by(id: params[:id]) end def download_request? true end + def upload_request? + false + end + def ci? false end @@ -49,4 +53,41 @@ describe LfsRequest do expect(assigns(:storage_project)).to eq(project) end end + + context 'user is authenticated without access to lfs' do + before do + allow(controller).to receive(:authenticate_user) + allow(controller).to receive(:authentication_result) do + Gitlab::Auth::Result.new + end + end + + context 'with access to the project' do + it 'returns 403' do + get :show, params: { id: project.id } + + expect(response.status).to eq(403) + end + end + + context 'without access to the project' do + context 'project does not exist' do + it 'returns 404' do + get :show, params: { id: 'does not exist' } + + expect(response.status).to eq(404) + end + end + + context 'project is private' do + let(:project) { create(:project, :private) } + + it 'returns 404' do + get :show, params: { id: project.id } + + expect(response.status).to eq(404) + end + end + end + end end diff --git a/spec/controllers/projects/autocomplete_sources_controller_spec.rb b/spec/controllers/projects/autocomplete_sources_controller_spec.rb index a9a058e7e1..f9c8e91d81 100644 --- a/spec/controllers/projects/autocomplete_sources_controller_spec.rb +++ b/spec/controllers/projects/autocomplete_sources_controller_spec.rb @@ -8,6 +8,10 @@ describe Projects::AutocompleteSourcesController do set(:issue) { create(:issue, project: project) } set(:user) { create(:user) } + def members_by_username(username) + json_response.find { |member| member['username'] == username } + end + describe 'GET members' do before do group.add_owner(user) @@ -17,22 +21,21 @@ describe Projects::AutocompleteSourcesController do it 'returns an array of member object' do get :members, format: :json, params: { namespace_id: group.path, project_id: project.path, type: issue.class.name, type_id: issue.id } - all = json_response.find {|member| member["username"] == 'all'} - the_group = json_response.find {|member| member["username"] == group.full_path} - the_user = json_response.find {|member| member["username"] == user.username} + expect(members_by_username('all').symbolize_keys).to include( + username: 'all', + name: 'All Project and Group Members', + count: 1) - expect(all.symbolize_keys).to include(username: 'all', - name: 'All Project and Group Members', - count: 1) + expect(members_by_username(group.full_path).symbolize_keys).to include( + type: group.class.name, + name: group.full_name, + avatar_url: group.avatar_url, + count: 1) - expect(the_group.symbolize_keys).to include(type: group.class.name, - name: group.full_name, - avatar_url: group.avatar_url, - count: 1) - - expect(the_user.symbolize_keys).to include(type: user.class.name, - name: user.name, - avatar_url: user.avatar_url) + expect(members_by_username(user.username).symbolize_keys).to include( + type: user.class.name, + name: user.name, + avatar_url: user.avatar_url) end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 9db1ac2a46..cb1a43fbbe 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -142,7 +142,7 @@ describe Projects::CommitsController do context 'token authentication' do context 'public project' do - it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do + it_behaves_like 'authenticates sessionless user', :show, :atom, { public: true, ignore_incrementing: true } do before do public_project = create(:project, :repository, :public) @@ -152,7 +152,7 @@ describe Projects::CommitsController do end context 'private project' do - it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do + it_behaves_like 'authenticates sessionless user', :show, :atom, { public: false, ignore_incrementing: true } do before do private_project = create(:project, :repository, :private) private_project.add_maintainer(user) diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb index d11ef24ef9..92b63ec96d 100644 --- a/spec/controllers/projects/error_tracking_controller_spec.rb +++ b/spec/controllers/projects/error_tracking_controller_spec.rb @@ -146,7 +146,7 @@ describe Projects::ErrorTrackingController do it 'redirects to sign-in page' do post :list_projects, params: list_projects_params - expect(response).to have_gitlab_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:redirect) end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index fab47aa470..e67f7a7c1c 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1349,7 +1349,7 @@ describe Projects::IssuesController do context 'private project with token authentication' do let(:private_project) { create(:project, :private) } - it_behaves_like 'authenticates sessionless user', :index, :atom do + it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do before do default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) @@ -1357,7 +1357,7 @@ describe Projects::IssuesController do end end - it_behaves_like 'authenticates sessionless user', :calendar, :ics do + it_behaves_like 'authenticates sessionless user', :calendar, :ics, ignore_incrementing: true do before do default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index b99b5d611f..f077b4c99f 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -41,7 +41,7 @@ describe Projects::TagsController do context 'private project with token authentication' do let(:private_project) { create(:project, :repository, :private) } - it_behaves_like 'authenticates sessionless user', :index, :atom do + it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do before do default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 083a1c1383..59da77dd01 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1053,7 +1053,7 @@ describe ProjectsController do context 'private project with token authentication' do let(:private_project) { create(:project, :private) } - it_behaves_like 'authenticates sessionless user', :show, :atom do + it_behaves_like 'authenticates sessionless user', :show, :atom, ignore_incrementing: true do before do default_params.merge!(id: private_project, namespace_id: private_project.namespace) diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 4fb72eb873..76d8ad1638 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -827,7 +827,10 @@ describe 'Pipelines', :js do context 'when project is private' do let(:project) { create(:project, :private, :repository) } - it { expect(page).to have_content 'You need to sign in' } + it 'redirects the user to sign_in and displays the flash alert' do + expect(page).to have_content 'You need to sign in' + expect(page.current_path).to eq("/users/sign_in") + end end end diff --git a/spec/features/projects/tags/user_views_tags_spec.rb b/spec/features/projects/tags/user_views_tags_spec.rb index f344b68271..bc570f502b 100644 --- a/spec/features/projects/tags/user_views_tags_spec.rb +++ b/spec/features/projects/tags/user_views_tags_spec.rb @@ -15,7 +15,7 @@ describe 'User views tags', :feature do it do visit project_tags_path(project, format: :atom) - expect(page).to have_gitlab_http_status(401) + expect(page.current_path).to eq("/users/sign_in") end end diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index ba41ded112..024bfe4d97 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -127,6 +127,88 @@ describe LabelsFinder do end end end + context 'when including labels from group projects with limited visibility' do + let(:finder) { described_class.new(user, group_id: group_4.id) } + let(:group_4) { create(:group) } + let(:limited_visibility_project) { create(:project, :public, group: group_4) } + let(:visible_project) { create(:project, :public, group: group_4) } + let!(:group_label_1) { create(:group_label, group: group_4) } + let!(:limited_visibility_label) { create(:label, project: limited_visibility_project) } + let!(:visible_label) { create(:label, project: visible_project) } + + shared_examples 'with full visibility' do + it 'returns all projects labels' do + expect(finder.execute).to eq [group_label_1, limited_visibility_label, visible_label] + end + end + + shared_examples 'with limited visibility' do + it 'returns only authorized projects labels' do + expect(finder.execute).to eq [group_label_1, visible_label] + end + end + + context 'when merge requests and issues are not visible for non members' do + before do + limited_visibility_project.project_feature.update!( + merge_requests_access_level: ProjectFeature::PRIVATE, + issues_access_level: ProjectFeature::PRIVATE + ) + end + + context 'when user is not a group member' do + it_behaves_like 'with limited visibility' + end + + context 'when user is a group member' do + before do + group_4.add_developer(user) + end + + it_behaves_like 'with full visibility' + end + end + + context 'when merge requests are not visible for non members' do + before do + limited_visibility_project.project_feature.update!( + merge_requests_access_level: ProjectFeature::PRIVATE + ) + end + + context 'when user is not a group member' do + it_behaves_like 'with full visibility' + end + + context 'when user is a group member' do + before do + group_4.add_developer(user) + end + + it_behaves_like 'with full visibility' + end + end + + context 'when issues are not visible for non members' do + before do + limited_visibility_project.project_feature.update!( + issues_access_level: ProjectFeature::PRIVATE + ) + end + + context 'when user is not a group member' do + it_behaves_like 'with full visibility' + end + + context 'when user is a group member' do + before do + group_4.add_developer(user) + end + + it_behaves_like 'with full visibility' + end + end + end context 'filtering by project_id' do context 'when include_ancestor_groups is true' do diff --git a/spec/fixtures/api/graphql/recursive-introspection.graphql b/spec/fixtures/api/graphql/recursive-introspection.graphql new file mode 100644 index 0000000000..db970bb14b --- /dev/null +++ b/spec/fixtures/api/graphql/recursive-introspection.graphql @@ -0,0 +1,57 @@ +query allSchemaTypes { + __schema { + types { + fields { + type{ + fields { + type { + fields { + type { + fields { + type { + fields { + type { + fields { + type { + fields { + type { + fields { + type { + fields { + type { + fields { + type { + fields { + type { + fields { + type { + fields { + name + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/spec/fixtures/api/graphql/recursive-query.graphql b/spec/fixtures/api/graphql/recursive-query.graphql new file mode 100644 index 0000000000..d1616c4de6 --- /dev/null +++ b/spec/fixtures/api/graphql/recursive-query.graphql @@ -0,0 +1,47 @@ +{ + project(fullPath: "gitlab-org/gitlab-ce") { + group { + projects { + edges { + node { + group { + projects { + edges { + node { + group { + projects { + edges { + node { + group { + projects { + edges { + node { + group { + projects { + edges { + node { + group { + description + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/spec/fixtures/api/graphql/small-recursive-introspection.graphql b/spec/fixtures/api/graphql/small-recursive-introspection.graphql new file mode 100644 index 0000000000..1025043b47 --- /dev/null +++ b/spec/fixtures/api/graphql/small-recursive-introspection.graphql @@ -0,0 +1,15 @@ +query allSchemaTypes { + __schema { + types { + fields { + type { + fields { + type { + name + } + } + } + } + } + } +} diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index f6e1720e11..f0bf547f77 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -1,18 +1,18 @@ require 'spec_helper' describe MarkupHelper do - let!(:project) { create(:project, :repository) } - - let(:user) { create(:user, username: 'gfm') } - let(:commit) { project.commit } - let(:issue) { create(:issue, project: project) } - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:snippet) { create(:project_snippet, project: project) } + set(:project) { create(:project, :repository) } + set(:user) do + user = create(:user, username: 'gfm') + project.add_maintainer(user) + user + end + set(:issue) { create(:issue, project: project) } + set(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + set(:snippet) { create(:project_snippet, project: project) } + let(:commit) { project.commit } before do - # Ensure the generated reference links aren't redacted - project.add_maintainer(user) - # Helper expects a @project instance variable helper.instance_variable_set(:@project, project) @@ -42,8 +42,8 @@ describe MarkupHelper do describe "override default project" do let(:actual) { issue.to_reference } - let(:second_project) { create(:project, :public) } - let(:second_issue) { create(:issue, project: second_project) } + set(:second_project) { create(:project, :public) } + set(:second_issue) { create(:issue, project: second_project) } it 'links to the issue' do expected = urls.project_issue_path(second_project, second_issue) @@ -53,7 +53,7 @@ describe MarkupHelper do describe 'uploads' do let(:text) { "![ImageTest](/uploads/test.png)" } - let(:group) { create(:group) } + set(:group) { create(:group) } subject { helper.markdown(text) } @@ -75,7 +75,7 @@ describe MarkupHelper do end describe "with a group in the context" do - let(:project_in_group) { create(:project, group: group) } + set(:project_in_group) { create(:project, group: group) } before do helper.instance_variable_set(:@group, group) @@ -233,38 +233,48 @@ describe MarkupHelper do end describe '#render_wiki_content' do + let(:wiki) { double('WikiPage', path: "file.#{extension}") } + let(:context) do + { + pipeline: :wiki, project: project, project_wiki: wiki, + page_slug: 'nested/page', issuable_state_filter_enabled: true + } + end + before do - @wiki = double('WikiPage') - allow(@wiki).to receive(:content).and_return('wiki content') - allow(@wiki).to receive(:slug).and_return('nested/page') - helper.instance_variable_set(:@project_wiki, @wiki) + expect(wiki).to receive(:content).and_return('wiki content') + expect(wiki).to receive(:slug).and_return('nested/page') + helper.instance_variable_set(:@project_wiki, wiki) end - it "uses Wiki pipeline for markdown files" do - allow(@wiki).to receive(:format).and_return(:markdown) + context 'when file is Markdown' do + let(:extension) { 'md' } - expect(helper).to receive(:markdown_unsafe).with('wiki content', - pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page", - issuable_state_filter_enabled: true) + it 'renders using #markdown_unsafe helper method' do + expect(helper).to receive(:markdown_unsafe).with('wiki content', context) - helper.render_wiki_content(@wiki) + helper.render_wiki_content(wiki) + end end - it "uses Asciidoctor for asciidoc files" do - allow(@wiki).to receive(:format).and_return(:asciidoc) + context 'when file is Asciidoc' do + let(:extension) { 'adoc' } - expect(helper).to receive(:asciidoc_unsafe).with('wiki content') + it 'renders using Gitlab::Asciidoc' do + expect(Gitlab::Asciidoc).to receive(:render) - helper.render_wiki_content(@wiki) + helper.render_wiki_content(wiki) + end end - it "uses the Gollum renderer for all other file types" do - allow(@wiki).to receive(:format).and_return(:rdoc) - formatted_content_stub = double('formatted_content') - expect(formatted_content_stub).to receive(:html_safe) - allow(@wiki).to receive(:formatted_content).and_return(formatted_content_stub) + context 'any other format' do + let(:extension) { 'foo' } - helper.render_wiki_content(@wiki) + it 'renders all other formats using Gitlab::OtherMarkup' do + expect(Gitlab::OtherMarkup).to receive(:render) + + helper.render_wiki_content(wiki) + end end end diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index 45d9022abe..1f5bb3ae13 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -2,7 +2,18 @@ require 'spec_helper' describe Gitlab::UrlBlocker do + include StubRequests + describe '#validate!' do + shared_examples 'validates URI and hostname' do + it 'runs the url validations' do + uri, hostname = subject + + expect(uri).to eq(Addressable::URI.parse(expected_uri)) + expect(hostname).to eq(expected_hostname) + end + end + context 'when URI is nil' do let(:import_url) { nil } @@ -34,6 +45,14 @@ describe Gitlab::UrlBlocker do expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34')) expect(hostname).to eq('example.org') end + + context 'when scheme is missing' do + let(:import_url) { '//example.org/path' } + + it 'raises and error' do + expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError) + end + end end context 'when the URL hostname is an IP address' do @@ -47,6 +66,32 @@ describe Gitlab::UrlBlocker do end end + context 'disabled require_absolute' do + subject { described_class.validate!(import_url, require_absolute: false) } + + context 'with scheme and hostname' do + let(:import_url) { 'https://example.org/path' } + + before do + stub_dns(import_url, ip_address: '93.184.216.34') + end + + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { 'https://93.184.216.34/path' } + let(:expected_hostname) { 'example.org' } + end + end + + context 'without scheme' do + let(:import_url) { '//93.184.216.34/path' } + + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { import_url } + let(:expected_hostname) { nil } + end + end + end + context 'disabled DNS rebinding protection' do context 'when URI is internal' do let(:import_url) { 'http://localhost' } @@ -520,6 +565,21 @@ describe Gitlab::UrlBlocker do end end + context 'when require_absolute is false' do + it 'allows paths' do + expect(described_class.blocked_url?('/foo/foo.bar', require_absolute: false)).to be false + end + + it 'allows absolute urls without paths' do + expect(described_class.blocked_url?('http://example.com', require_absolute: false)).to be false + end + + it 'paths must begin with a slash' do + expect(described_class.blocked_url?('foo/foo.bar', require_absolute: false)).to be true + expect(described_class.blocked_url?('', require_absolute: false)).to be true + end + end + it 'blocks urls with invalid ip address' do stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 4f7a6d102b..9a27f60877 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -17,6 +17,7 @@ describe ApplicationSetting do let(:http) { 'http://example.com' } let(:https) { 'https://example.com' } let(:ftp) { 'ftp://example.com' } + let(:javascript) { 'javascript:alert(window.opener.document.location)' } it { is_expected.to allow_value(nil).for(:home_page_url) } it { is_expected.to allow_value(http).for(:home_page_url) } @@ -48,6 +49,11 @@ describe ApplicationSetting do it { is_expected.not_to allow_value(nil).for(:outbound_local_requests_whitelist) } it { is_expected.to allow_value([]).for(:outbound_local_requests_whitelist) } + it { is_expected.to allow_value(http).for(:grafana_url) } + it { is_expected.to allow_value(https).for(:grafana_url) } + it { is_expected.not_to allow_value(ftp).for(:grafana_url) } + it { is_expected.not_to allow_value(javascript).for(:grafana_url) } + context "when user accepted let's encrypt terms of service" do before do setting.update(lets_encrypt_terms_of_service_accepted: true) diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 3704a2d468..d7c522247e 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -206,6 +206,14 @@ describe Milestone do end end + describe '#to_ability_name' do + it 'returns milestone' do + milestone = build(:milestone) + + expect(milestone.to_ability_name).to eq('milestone') + end + end + describe '.search' do let(:milestone) { create(:milestone, title: 'foo', description: 'bar') } diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 927fbdb93d..fe7e1e8155 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -340,6 +340,63 @@ describe Note do expect(label_note.cross_reference?).to be_falsy end end + + context 'when system note metadata is not present' do + let(:note) { build(:note, :system) } + + before do + allow(note).to receive(:system_note_metadata).and_return(nil) + end + + it 'delegates to the system note service' do + expect(SystemNoteService).to receive(:cross_reference?).with(note.note) + + note.cross_reference? + end + end + + context 'with a system note' do + let(:issue) { create(:issue, project: create(:project, :repository)) } + let(:note) { create(:system_note, note: "test", noteable: issue, project: issue.project) } + + shared_examples 'system_note_metadata includes note action' do + it 'delegates to the cross-reference regex' do + expect(note).to receive(:matches_cross_reference_regex?) + + note.cross_reference? + end + end + + context 'with :label action' do + let!(:metadata) {create(:system_note_metadata, note: note, action: :label)} + + it_behaves_like 'system_note_metadata includes note action' + + it { expect(note.cross_reference?).to be_falsy } + + context 'with cross reference label note' do + let(:label) { create(:label, project: issue.project)} + let(:note) { create(:system_note, note: "added #{label.to_reference} label", noteable: issue, project: issue.project) } + + it { expect(note.cross_reference?).to be_truthy } + end + end + + context 'with :milestone action' do + let!(:metadata) {create(:system_note_metadata, note: note, action: :milestone)} + + it_behaves_like 'system_note_metadata includes note action' + + it { expect(note.cross_reference?).to be_falsy } + + context 'with cross reference milestone note' do + let(:milestone) { create(:milestone, project: issue.project)} + let(:note) { create(:system_note, note: "added #{milestone.to_reference} milestone", noteable: issue, project: issue.project) } + + it { expect(note.cross_reference?).to be_truthy } + end + end + end end describe 'clear_blank_line_code!' do @@ -539,24 +596,30 @@ describe Note do end describe '#to_ability_name' do - it 'returns snippet for a project snippet note' do - expect(build(:note_on_project_snippet).to_ability_name).to eq('project_snippet') + it 'returns note' do + expect(build(:note).to_ability_name).to eq('note') + end + end + + describe '#noteable_ability_name' do + it 'returns project_snippet for a project snippet note' do + expect(build(:note_on_project_snippet).noteable_ability_name).to eq('project_snippet') end it 'returns personal_snippet for a personal snippet note' do - expect(build(:note_on_personal_snippet).to_ability_name).to eq('personal_snippet') + expect(build(:note_on_personal_snippet).noteable_ability_name).to eq('personal_snippet') end it 'returns merge_request for an MR note' do - expect(build(:note_on_merge_request).to_ability_name).to eq('merge_request') + expect(build(:note_on_merge_request).noteable_ability_name).to eq('merge_request') end it 'returns issue for an issue note' do - expect(build(:note_on_issue).to_ability_name).to eq('issue') + expect(build(:note_on_issue).noteable_ability_name).to eq('issue') end - it 'returns issue for a commit note' do - expect(build(:note_on_commit).to_ability_name).to eq('commit') + it 'returns commit for a commit note' do + expect(build(:note_on_commit).noteable_ability_name).to eq('commit') end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index de5fe9ee8a..91dcdcaecb 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3303,7 +3303,7 @@ describe Project do end end - describe '.ids_with_milestone_available_for' do + describe '.ids_with_issuables_available_for' do let!(:user) { create(:user) } it 'returns project ids with milestones available for user' do @@ -3313,7 +3313,7 @@ describe Project do project_4 = create(:project, :public) project_4.project_feature.update(issues_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE ) - project_ids = described_class.ids_with_milestone_available_for(user).pluck(:id) + project_ids = described_class.ids_with_issuables_available_for(user).pluck(:id) expect(project_ids).to include(project_2.id, project_3.id) expect(project_ids).not_to include(project_1.id, project_4.id) @@ -4334,6 +4334,14 @@ describe Project do end end + describe '#to_ability_name' do + it 'returns project' do + project = build(:project_empty_repo) + + expect(project.to_ability_name).to eq('project') + end + end + describe '#execute_hooks' do let(:data) { { ref: 'refs/heads/master', data: 'data' } } it 'executes active projects hooks with the specified scope' do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index 18c62c917d..9014276dcf 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -439,6 +439,23 @@ describe WikiPage do end end + describe '#path' do + let(:path) { 'mypath.md' } + let(:wiki_page) { instance_double('Gitlab::Git::WikiPage', path: path).as_null_object } + + it 'returns the path when persisted' do + page = described_class.new(wiki, wiki_page, true) + + expect(page.path).to eq(path) + end + + it 'returns nil when not persisted' do + page = described_class.new(wiki, wiki_page, false) + + expect(page.path).to be_nil + end + end + describe '#directory' do context 'when the page is at the root directory' do it 'returns an empty string' do diff --git a/spec/policies/commit_policy_spec.rb b/spec/policies/commit_policy_spec.rb index 41f6fb0842..40183f51e9 100644 --- a/spec/policies/commit_policy_spec.rb +++ b/spec/policies/commit_policy_spec.rb @@ -8,28 +8,42 @@ describe CommitPolicy do let(:commit) { project.repository.head_commit } let(:policy) { described_class.new(user, commit) } + shared_examples 'can read commit and create a note' do + it 'can read commit' do + expect(policy).to be_allowed(:read_commit) + end + + it 'can create a note' do + expect(policy).to be_allowed(:create_note) + end + end + + shared_examples 'cannot read commit nor create a note' do + it 'can not read commit' do + expect(policy).to be_disallowed(:read_commit) + end + + it 'can not create a note' do + expect(policy).to be_disallowed(:create_note) + end + end + context 'when project is public' do let(:project) { create(:project, :public, :repository) } - it 'can read commit and create a note' do - expect(policy).to be_allowed(:read_commit) - end + it_behaves_like 'can read commit and create a note' context 'when repository access level is private' do let(:project) { create(:project, :public, :repository, :repository_private) } - it 'can not read commit and create a note' do - expect(policy).to be_disallowed(:read_commit) - end + it_behaves_like 'cannot read commit nor create a note' context 'when the user is a project member' do before do project.add_developer(user) end - it 'can read commit and create a note' do - expect(policy).to be_allowed(:read_commit) - end + it_behaves_like 'can read commit and create a note' end end end @@ -37,9 +51,7 @@ describe CommitPolicy do context 'when project is private' do let(:project) { create(:project, :private, :repository) } - it 'can not read commit and create a note' do - expect(policy).to be_disallowed(:read_commit) - end + it_behaves_like 'cannot read commit nor create a note' context 'when the user is a project member' do before do @@ -50,6 +62,18 @@ describe CommitPolicy do expect(policy).to be_allowed(:read_commit) end end + + context 'when the user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like 'cannot read commit nor create a note' + + it 'cannot download code' do + expect(policy).to be_disallowed(:download_code) + end + end end end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index be55d94dae..ede07a5f6a 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -335,6 +335,88 @@ describe GroupPolicy do end end + context 'transfer_projects' do + shared_examples_for 'allowed to transfer projects' do + before do + group.update(project_creation_level: project_creation_level) + end + + it { is_expected.to be_allowed(:transfer_projects) } + end + + shared_examples_for 'not allowed to transfer projects' do + before do + group.update(project_creation_level: project_creation_level) + end + + it { is_expected.to be_disallowed(:transfer_projects) } + end + + context 'reporter' do + let(:current_user) { reporter } + + it_behaves_like 'not allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } + end + + it_behaves_like 'not allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } + end + + it_behaves_like 'not allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS } + end + end + + context 'developer' do + let(:current_user) { developer } + + it_behaves_like 'not allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } + end + + it_behaves_like 'not allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } + end + + it_behaves_like 'not allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS } + end + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it_behaves_like 'not allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } + end + + it_behaves_like 'allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } + end + + it_behaves_like 'allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS } + end + end + + context 'owner' do + let(:current_user) { owner } + + it_behaves_like 'not allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } + end + + it_behaves_like 'allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } + end + + it_behaves_like 'allowed to transfer projects' do + let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS } + end + end + end + context "create_projects" do context 'when group has no project creation level set' do let(:group) { create(:group, project_creation_level: nil) } diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb index 99fa8b1fe4..b48185838e 100644 --- a/spec/policies/namespace_policy_spec.rb +++ b/spec/policies/namespace_policy_spec.rb @@ -6,7 +6,7 @@ describe NamespacePolicy do let(:admin) { create(:admin) } let(:namespace) { create(:namespace, owner: owner) } - let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace] } + let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace, :transfer_projects] } subject { described_class.new(current_user, namespace) } @@ -31,6 +31,7 @@ describe NamespacePolicy do let(:owner) { create(:user, projects_limit: 0) } it { is_expected.to be_disallowed(:create_projects) } + it { is_expected.to be_disallowed(:transfer_projects) } end end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 1be8883bd3..deeab82a80 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -322,7 +322,7 @@ describe API::CommitStatuses do it 'responds with bad request status and validation errors' do expect(response).to have_gitlab_http_status(400) expect(json_response['message']['target_url']) - .to include 'is blocked: Only allowed schemes are http, https' + .to include 'is blocked: must be absolute' end end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index 28676bb02f..c6ff459e3a 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -13,7 +13,7 @@ describe 'GitlabSchema configurations' do subject - expect(graphql_errors.flatten.first['message']).to include('which exceeds max complexity of 1') + expect_graphql_errors_to_include /which exceeds max complexity of 1/ end end end @@ -21,12 +21,11 @@ describe 'GitlabSchema configurations' do describe '#max_depth' do context 'when query depth is too high' do it 'shows error' do - errors = { "message" => "Query has depth of 2, which exceeds max depth of 1" } allow(GitlabSchema).to receive(:max_query_depth).and_return 1 subject - expect(graphql_errors.flatten).to include(errors) + expect_graphql_errors_to_include /exceeds max depth/ end end @@ -36,7 +35,42 @@ describe 'GitlabSchema configurations' do subject - expect(Array.wrap(graphql_errors).compact).to be_empty + expect_graphql_errors_to_be_empty + end + end + end + end + + context 'depth, complexity and recursion checking' do + context 'unauthenticated recursive queries' do + context 'a not-quite-recursive-enough introspective query' do + it 'succeeds' do + query = File.read(Rails.root.join('spec/fixtures/api/graphql/small-recursive-introspection.graphql')) + + post_graphql(query, current_user: nil) + + expect_graphql_errors_to_be_empty + end + end + + context 'a deep but simple recursive introspective query' do + it 'fails due to recursion' do + query = File.read(Rails.root.join('spec/fixtures/api/graphql/recursive-introspection.graphql')) + + post_graphql(query, current_user: nil) + + expect_graphql_errors_to_include [/Recursive query/] + end + end + + context 'a deep recursive non-introspective query' do + it 'fails due to recursion, complexity and depth' do + allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 + query = File.read(Rails.root.join('spec/fixtures/api/graphql/recursive-query.graphql')) + + post_graphql(query, current_user: nil) + + expect_graphql_errors_to_include [/Recursive query/, /exceeds max complexity/, /exceeds max depth/] end end end @@ -86,7 +120,7 @@ describe 'GitlabSchema configurations' do # Expect errors for each query expect(graphql_errors.size).to eq(3) graphql_errors.each do |single_query_errors| - expect(single_query_errors.first['message']).to include('which exceeds max complexity of 4') + expect_graphql_errors_to_include(/which exceeds max complexity of 4/) end end end @@ -103,12 +137,12 @@ describe 'GitlabSchema configurations' do end context 'when IntrospectionQuery' do - it 'is not too complex' do + it 'is not too complex nor recursive' do query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) post_graphql(query, current_user: nil) - expect(graphql_errors).to be_nil + expect_graphql_errors_to_be_empty end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 15d6db4276..1bdeae959c 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1673,6 +1673,38 @@ describe API::MergeRequests do expect(json_response['state']).to eq('closed') expect(json_response['force_remove_source_branch']).to be_truthy end + + context 'with a merge request across forks' do + let(:fork_owner) { create(:user) } + let(:source_project) { fork_project(project, fork_owner) } + let(:target_project) { project } + + let(:merge_request) do + create(:merge_request, + source_project: source_project, + target_project: target_project, + source_branch: 'fixes', + merge_params: { 'force_remove_source_branch' => false }) + end + + it 'is true for an authorized user' do + put api("/projects/#{target_project.id}/merge_requests/#{merge_request.iid}", fork_owner), params: { state_event: 'close', remove_source_branch: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['state']).to eq('closed') + expect(json_response['force_remove_source_branch']).to be true + end + + it 'is false for an unauthorized user' do + expect do + put api("/projects/#{target_project.id}/merge_requests/#{merge_request.iid}", target_project.owner), params: { state_event: 'close', remove_source_branch: true } + end.not_to change { merge_request.reload.merge_params } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['state']).to eq('closed') + expect(json_response['force_remove_source_branch']).to be false + end + end end context "to close a MR" do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 1d7ca85cdd..74fe683f05 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -2609,6 +2609,22 @@ describe API::Projects do expect(response).to have_gitlab_http_status(400) end end + + context 'when authenticated as developer' do + before do + group.add_developer(user) + end + + context 'target namespace allows developers to create projects' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + it 'fails transferring the project to the target namespace' do + put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id } + + expect(response).to have_gitlab_http_status(400) + end + end + end end it_behaves_like 'custom attributes endpoints', 'projects' do diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb index a409f21a7e..0a6bcb1bad 100644 --- a/spec/services/auto_merge/base_service_spec.rb +++ b/spec/services/auto_merge/base_service_spec.rb @@ -51,7 +51,7 @@ describe AutoMerge::BaseService do expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'") expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18') expect(merge_request.merge_params['should_remove_source_branch']).to eq(false) - expect(merge_request.merge_params['squash']).to eq(false) + expect(merge_request.squash).to eq(false) expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md') end end @@ -108,7 +108,6 @@ describe AutoMerge::BaseService do 'commit_message' => "Merge branch 'patch-12' into 'master'", 'sha' => "200fcc9c260f7219eaf0daba87d818f0922c5b18", 'should_remove_source_branch' => false, - 'squash' => false, 'squash_commit_message' => "Update README.md" } end diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb index 931b52470c..ccbb4e7c30 100644 --- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb @@ -59,8 +59,9 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do it 'sets the params, merge_user, and flag' do expect(merge_request).to be_valid expect(merge_request.merge_when_pipeline_succeeds).to be_truthy - expect(merge_request.merge_params).to include commit_message: 'Awesome message' + expect(merge_request.merge_params).to include 'commit_message' => 'Awesome message' expect(merge_request.merge_user).to be user + expect(merge_request.auto_merge_strategy).to eq AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS end it 'creates a system note' do @@ -73,7 +74,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do end context 'already approved' do - let(:service) { described_class.new(project, user, new_key: true) } + let(:service) { described_class.new(project, user, should_remove_source_branch: true) } let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } before do @@ -90,7 +91,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds) service.execute(mr_merge_if_green_enabled) - expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key) + expect(mr_merge_if_green_enabled.merge_params).to have_key('should_remove_source_branch') end end end diff --git a/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb new file mode 100644 index 0000000000..5b653aa331 --- /dev/null +++ b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +describe MergeRequests::AssignsMergeParams do + it 'raises an error when used from an instance that does not respond to #current_user' do + define_class = -> { Class.new { include MergeRequests::AssignsMergeParams }.new } + + expect { define_class.call }.to raise_error %r{can not be included in (.*) without implementing #current_user} + end + + describe '#assign_allowed_merge_params' do + let(:merge_request) { build(:merge_request) } + + let(:params) do + { commit_message: 'Commit Message', + 'should_remove_source_branch' => true, + unknown_symbol: 'Unknown symbol', + 'unknown_string' => 'Unknown String' } + end + + subject(:merge_request_service) do + Class.new do + attr_accessor :current_user + + include MergeRequests::AssignsMergeParams + + def initialize(user) + @current_user = user + end + end + end + + it 'only assigns known parameters to the merge request' do + service = merge_request_service.new(merge_request.author) + + service.assign_allowed_merge_params(merge_request, params) + + expect(merge_request.merge_params).to eq('commit_message' => 'Commit Message', 'should_remove_source_branch' => true) + end + + it 'returns a hash without the known merge params' do + service = merge_request_service.new(merge_request.author) + + result = service.assign_allowed_merge_params(merge_request, params) + + expect(result).to eq({ 'unknown_symbol' => 'Unknown symbol', 'unknown_string' => 'Unknown String' }) + end + + context 'the force_remove_source_branch param' do + let(:params) { { force_remove_source_branch: true } } + + it 'assigns the param if the user is allowed to do that' do + service = merge_request_service.new(merge_request.author) + + result = service.assign_allowed_merge_params(merge_request, params) + + expect(merge_request.force_remove_source_branch?).to be true + expect(result).to be_empty + end + + it 'only removes the param if the user is not allowed to do that' do + service = merge_request_service.new(build(:user)) + + result = service.assign_allowed_merge_params(merge_request, params) + + expect(merge_request.force_remove_source_branch?).to be_falsy + expect(result).to be_empty + end + end + end +end diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb index 730fccc599..a272a60418 100644 --- a/spec/services/error_tracking/list_projects_service_spec.rb +++ b/spec/services/error_tracking/list_projects_service_spec.rb @@ -50,6 +50,19 @@ describe ErrorTracking::ListProjectsService do end end + context 'masked param token' do + let(:params) { ActionController::Parameters.new(token: "*********", api_host: new_api_host) } + + before do + expect(error_tracking_setting).to receive(:list_sentry_projects) + .and_return({ projects: [] }) + end + + it 'uses database token' do + expect { subject.execute }.not_to change { error_tracking_setting.token } + end + end + context 'sentry client raises exception' do context 'Sentry::Client::Error' do before do diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index f18239f6d3..56f765cd39 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -67,8 +67,9 @@ describe MergeRequests::BuildService do expect(merge_request.force_remove_source_branch?).to be_falsey end - context 'with force_remove_source_branch parameter' do + context 'with force_remove_source_branch parameter when the user is authorized' do let(:mr_params) { params.merge(force_remove_source_branch: '1') } + let(:source_project) { fork_project(project, user) } let(:merge_request) { described_class.new(project, user, mr_params).execute } it 'assigns force_remove_source_branch' do diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 9688e02d6a..bcfd990573 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -644,5 +644,29 @@ describe MergeRequests::UpdateService, :mailer do expect(merge_request.allow_collaboration).to be_truthy end end + + context 'updating `force_remove_source_branch`' do + let(:target_project) { create(:project, :repository, :public) } + let(:source_project) { fork_project(target_project, nil, repository: true) } + let(:user) { target_project.owner } + let(:merge_request) do + create(:merge_request, + source_project: source_project, + source_branch: 'fixes', + target_project: target_project) + end + + it "cannot be done by members of the target project when they don't have access" do + expect { update_merge_request(force_remove_source_branch: true) } + .not_to change { merge_request.reload.force_remove_source_branch? }.from(nil) + end + + it 'can be done by members of the target project if they can push to the source project' do + source_project.add_developer(user) + + expect { update_merge_request(force_remove_source_branch: true) } + .to change { merge_request.reload.force_remove_source_branch? }.from(nil).to(true) + end + end end end diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb index 7e765659b9..f1e6116fe3 100644 --- a/spec/services/projects/operations/update_service_spec.rb +++ b/spec/services/projects/operations/update_service_spec.rb @@ -145,6 +145,27 @@ describe Projects::Operations::UpdateService do end end + context 'with masked param token' do + let(:params) do + { + error_tracking_setting_attributes: { + enabled: false, + token: '*' * 8 + } + } + end + + before do + create(:project_error_tracking_setting, project: project, token: 'token') + end + + it 'does not update token' do + expect(result[:status]).to eq(:success) + + expect(project.error_tracking_setting.token).to eq('token') + end + end + context 'with invalid parameters' do let(:params) { {} } diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index 4def83513a..239d28557e 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -57,4 +57,108 @@ describe Projects::ParticipantsService do end end end + + describe '#project_members' do + subject(:usernames) { service.project_members.map { |member| member[:username] } } + + context 'when there is a project in group namespace' do + set(:public_group) { create(:group, :public) } + set(:public_project) { create(:project, :public, namespace: public_group)} + + set(:public_group_owner) { create(:user) } + + let(:service) { described_class.new(public_project, create(:user)) } + + before do + public_group.add_owner(public_group_owner) + end + + it 'returns members of a group' do + expect(usernames).to include(public_group_owner.username) + end + end + + context 'when there is a private group and a public project' do + set(:public_group) { create(:group, :public) } + set(:private_group) { create(:group, :private, :nested) } + set(:public_project) { create(:project, :public, namespace: public_group)} + + set(:project_issue) { create(:issue, project: public_project)} + + set(:public_group_owner) { create(:user) } + set(:private_group_member) { create(:user) } + set(:public_project_maintainer) { create(:user) } + set(:private_group_owner) { create(:user) } + + set(:group_ancestor_owner) { create(:user) } + + before(:context) do + public_group.add_owner public_group_owner + private_group.add_developer private_group_member + public_project.add_maintainer public_project_maintainer + + private_group.add_owner private_group_owner + private_group.parent.add_owner group_ancestor_owner + end + + context 'when the private group is invited to the public project' do + before(:context) do + create(:project_group_link, group: private_group, project: public_project) + end + + context 'when a user who is outside the public project and the private group is signed in' do + let(:service) { described_class.new(public_project, create(:user)) } + + it 'does not return the private group' do + expect(usernames).not_to include(private_group.name) + end + + it 'does not return private group members' do + expect(usernames).not_to include(private_group_member.username) + end + + it 'returns the project maintainer' do + expect(usernames).to include(public_project_maintainer.username) + end + + it 'returns project members from an invited public group' do + invited_public_group = create(:group, :public) + invited_public_group.add_owner create(:user) + + create(:project_group_link, group: invited_public_group, project: public_project) + + expect(usernames).to include(invited_public_group.users.first.username) + end + + it 'does not return ancestors of the private group' do + expect(usernames).not_to include(group_ancestor_owner.username) + end + end + + context 'when private group owner is signed in' do + let(:service) { described_class.new(public_project, private_group_owner) } + + it 'returns private group members' do + expect(usernames).to include(private_group_member.username) + end + + it 'returns ancestors of the the private group' do + expect(usernames).to include(group_ancestor_owner.username) + end + end + + context 'when the namespace owner of the public project is signed in' do + let(:service) { described_class.new(public_project, public_group_owner) } + + it 'returns private group members' do + expect(usernames).to include(private_group_member.username) + end + + it 'does not return members of the ancestral groups of the private group' do + expect(usernames).to include(group_ancestor_owner.username) + end + end + end + end + end end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index a47c10d991..cb784de0b1 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -222,6 +222,24 @@ describe Projects::TransferService do it { expect(project.errors[:new_namespace]).to include('Project with same name or path in target namespace already exists') } end + context 'target namespace allows developers to create projects' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + context 'the user is a member of the target namespace with developer permissions' do + subject(:transfer_project_result) { transfer_project(project, user, group) } + + before do + group.add_developer(user) + end + + it 'does not allow project transfer to the target namespace' do + expect(transfer_project_result).to eq false + expect(project.namespace).to eq(user.namespace) + expect(project.errors[:new_namespace]).to include('Transfer failed, please contact an admin.') + end + end + end + def transfer_project(project, user, new_namespace) service = Projects::TransferService.new(project, user) diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb index b5149a0fcb..bc95fcd6b8 100644 --- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb +++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb @@ -34,8 +34,15 @@ shared_examples 'authenticates sessionless user' do |path, format, params| context 'when the personal access token has no api scope', unless: params[:public] do it 'does not log the user in' do - expect(authentication_metrics) - .to increment(:user_unauthenticated_counter) + # Several instances of where these specs are shared route the request + # through ApplicationController#route_not_found which does not involve + # the usual auth code from Devise, so does not increment the + # :user_unauthenticated_counter + # + unless params[:ignore_incrementing] + expect(authentication_metrics) + .to increment(:user_unauthenticated_counter) + end personal_access_token.update(scopes: [:read_user]) @@ -84,8 +91,15 @@ shared_examples 'authenticates sessionless user' do |path, format, params| end it "doesn't log the user in otherwise", unless: params[:public] do - expect(authentication_metrics) - .to increment(:user_unauthenticated_counter) + # Several instances of where these specs are shared route the request + # through ApplicationController#route_not_found which does not involve + # the usual auth code from Devise, so does not increment the + # :user_unauthenticated_counter + # + unless params[:ignore_incrementing] + expect(authentication_metrics) + .to increment(:user_unauthenticated_counter) + end get path, params: default_params.merge(private_token: 'token') diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index d86371d70b..e62bce3f32 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -117,6 +117,7 @@ module GraphqlHelpers def all_graphql_fields_for(class_name, parent_types = Set.new) allow_unlimited_graphql_complexity allow_unlimited_graphql_depth + allow_high_graphql_recursion type = GitlabSchema.types[class_name.to_s] return "" unless type @@ -176,6 +177,23 @@ module GraphqlHelpers end end + def expect_graphql_errors_to_include(regexes_to_match) + raise "No errors. Was expecting to match #{regexes_to_match}" if graphql_errors.nil? || graphql_errors.empty? + + error_messages = flattened_errors.collect { |error_hash| error_hash["message"] } + Array.wrap(regexes_to_match).flatten.each do |regex| + expect(error_messages).to include a_string_matching regex + end + end + + def expect_graphql_errors_to_be_empty + expect(flattened_errors).to be_empty + end + + def flattened_errors + Array.wrap(graphql_errors).flatten.compact + end + # Raises an error if no response is found def graphql_mutation_response(mutation_name) graphql_data.fetch(GraphqlHelpers.fieldnamerize(mutation_name)) @@ -223,6 +241,10 @@ module GraphqlHelpers allow_any_instance_of(GitlabSchema).to receive(:max_depth).and_return nil allow(GitlabSchema).to receive(:max_query_depth).with(any_args).and_return nil end + + def allow_high_graphql_recursion + allow_any_instance_of(Gitlab::Graphql::QueryAnalyzers::RecursionAnalyzer).to receive(:recursion_threshold).and_return 1000 + end end # This warms our schema, doing this as part of loading the helpers to avoid diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb index f3f9abb7da..914bf50632 100644 --- a/spec/support/shared_examples/controllers/todos_shared_examples.rb +++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb @@ -39,7 +39,7 @@ shared_examples 'todos actions' do post_create end.to change { user.todos.count }.by(0) - expect(response).to have_gitlab_http_status(parent.is_a?(Group) ? 401 : 302) + expect(response).to have_gitlab_http_status(302) end end end diff --git a/spec/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb index 387e84b2d0..d9e2158562 100644 --- a/spec/validators/addressable_url_validator_spec.rb +++ b/spec/validators/addressable_url_validator_spec.rb @@ -312,4 +312,67 @@ describe AddressableUrlValidator do end end end + + context 'when require_absolute is' do + let(:validator) { described_class.new(attributes: [:link_url], require_absolute: require_absolute) } + let(:valid_relative_url) { '/relative/path' } + let(:invalid_relative_url) { 'relative/path' } + let(:absolute_url) { 'https://example.com' } + + context 'true' do + let(:require_absolute) { true } + + it 'prevents valid relative urls' do + badge.link_url = valid_relative_url + + subject + + expect(badge.errors).to be_present + end + + it 'prevents invalid relative urls' do + badge.link_url = invalid_relative_url + + subject + + expect(badge.errors).to be_present + end + + it 'allows absolute urls' do + badge.link_url = absolute_url + + subject + + expect(badge.errors).to be_empty + end + end + + context 'false' do + let(:require_absolute) { false } + + it 'allows valid relative urls' do + badge.link_url = valid_relative_url + + subject + + expect(badge.errors).to be_empty + end + + it 'prevents invalid relative urls' do + badge.link_url = invalid_relative_url + + subject + + expect(badge.errors).to be_present + end + + it 'allows absolute urls' do + badge.link_url = absolute_url + + subject + + expect(badge.errors).to be_empty + end + end + end end