diff --git a/core/includes/common.inc b/core/includes/common.inc
index 5d88545..f96c3aa 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -488,33 +488,6 @@ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) {
  */
 
 /**
- * Generates an internal or external URL.
- *
- * When creating links in modules, consider whether l() could be a better
- * alternative than url().
- *
- * @see \Drupal\Core\Url::fromUri()
- * @see \Drupal\Core\Url::fromRoute()
- * @see \Drupal\Core\Url::toString()
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.0.
- *   Use \Drupal\Core\Url::fromRoute() for internal paths served by Drupal
- *   controllers or \Drupal\Core\Url::fromUri() for external paths or
- *   non-controller or sub-domain URIs such as core/install.php. Note that
- *   \Drupal\Core\Url::fromUri() expects a valid URI including the scheme. URIs
- *   from the same sub-domain that are not handled by Drupal controllers should
- *   use the base: scheme. For example:
- * @code
- * $installer_url = \Drupal\Core\Url::fromUri('base:core/install.php')->toString();
- * $external_url = \Drupal\Core\Url::fromUri('http://example.com', ['query' => ['foo' => 'bar']])->toString();
- * $internal_url = \Drupal\Core\Url::fromRoute('system.admin')->toString();
- * @endcode
- */
-function _url($path = NULL, array $options = array()) {
-  return \Drupal::urlGenerator()->generateFromPath($path, $options);
-}
-
-/**
  * Formats an attribute string for an HTTP header.
  *
  * @param $attributes
@@ -535,152 +508,6 @@ function drupal_http_header_attributes(array $attributes = array()) {
 }
 
 /**
- * Formats an internal or external URL link as an HTML anchor tag.
- *
- * This function correctly handles aliased paths and adds an 'active' class
- * attribute to links that point to the current page (for theming), so all
- * internal links output by modules should be generated by this function if
- * possible.
- *
- * However, for links enclosed in translatable text you should use t() and
- * embed the HTML anchor tag directly in the translated string. For example:
- * @code
- * t('Visit the <a href="@url">settings</a> page', array('@url' => \Drupal::url('system.admin')));
- * @endcode
- * This keeps the context of the link title ('settings' in the example) for
- * translators.
- *
- * This function does not support generating links from internal routes. For
- * that use \Drupal\Core\Utility\LinkGenerator::generate(), which is exposed via
- * the 'link_generator' service. It requires an internal route name and does not
- * support external URLs. Using Drupal 7 style system paths should be avoided if
- * possible but l() should still be used when rendering links to external URLs.
- *
- * @param string|array $text
- *   The link text for the anchor tag as a translated string or render array.
- * @param string $path
- *   The internal path or external URL being linked to, such as "node/34" or
- *   "http://example.com/foo". After the url() function is called to construct
- *   the URL from $path and $options, the resulting URL is passed through
- *   \Drupal\Component\Utility\String::checkPlain() before it is inserted into
- *   the HTML anchor tag, to ensure well-formed HTML. See url() for more
- *   information and notes.
- * @param array $options
- *   An associative array of additional options. Defaults to an empty array. It
- *   may contain the following elements.
- *   - 'attributes': An associative array of HTML attributes to apply to the
- *     anchor tag. If element 'class' is included, it must be an array; 'title'
- *     must be a string; other elements are more flexible, as they just need
- *     to work as an argument for the constructor of the class
- *     Drupal\Core\Template\Attribute($options['attributes']).
- *   - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
- *     example, to make an image tag into a link, this must be set to TRUE, or
- *     you will see the escaped HTML image tag. $text is not sanitized if
- *     'html' is TRUE. The calling function must ensure that $text is already
- *     safe.
- *   - 'language': An optional language object. If the path being linked to is
- *     internal to the site, $options['language'] is used to determine whether
- *     the link is "active", or pointing to the current page (the language as
- *     well as the path must match). This element is also used by url().
- *   - 'set_active_class': Whether l() should compare the $path, language and
- *     query options to the current URL to determine whether the link is
- *     "active". Defaults to FALSE. If TRUE, an "active" class will be applied
- *     to the link. It is important to use this sparingly since it is usually
- *     unnecessary and requires extra processing.
- *     For anonymous users, the "active" class will be calculated on the server,
- *     because most sites serve each anonymous user the same cached page anyway.
- *     For authenticated users, the "active" class will be calculated on the
- *     client (through JavaScript), only data- attributes are added to links to
- *     prevent breaking the render cache. The JavaScript is added in
- *     system_page_attachments().
- *   - Additional $options elements used by the url() function.
- *
- * @return string
- *   An HTML string containing a link to the given path.
- *
- * @see _url()
- * @see system_page_attachments()
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.0.
- *   Use \Drupal::l($text, $url) where $url is an instance of
- *   \Drupal\Core\Url. To build a \Drupal\Core\Url object for internal paths
- *   served by Drupal controllers use \Drupal\Core\Url::fromRoute(). For
- *   external paths or non-controller or sub-domain URIs such as
- *   core/install.php use \Drupal\Core\Url::fromUri(). Note that
- *   \Drupal\Core\Url::fromUri() expects a valid URI including the scheme. URIs
- *   from the same sub-domain that are not handled by Drupal controllers should
- *   be prepended with base:. For example:
- * @code
- * $installer_url = \Drupal\Core\Url::fromUri('base:core/install.php')->toString();
- * $installer_link = \Drupal::l($text, $installer_url);
- * $external_url = \Drupal\Core\Url::fromUri('http://example.com', ['query' => ['foo' => 'bar']])->toString();
- * $external_link = \Drupal::l($text, $external_url);
- * $internal_url = \Drupal\Core\Url::fromRoute('system.admin')->toString();
- * $internal_link = \Drupal::l($text, $internal_url);
- * @endcode
- */
-function _l($text, $path, array $options = array()) {
-  // Start building a structured representation of our link to be altered later.
-  $variables = array(
-    'text' => is_array($text) ? drupal_render($text) : $text,
-    'path' => $path,
-    'options' => $options,
-  );
-
-  // Merge in default options.
-  $variables['options'] += array(
-    'attributes' => array(),
-    'query' => array(),
-    'html' => FALSE,
-    'language' => NULL,
-    'set_active_class' => FALSE,
-  );
-
-  // Add a hreflang attribute if we know the language of this link's url and
-  // hreflang has not already been set.
-  if (!empty($variables['options']['language']) && !isset($variables['options']['attributes']['hreflang'])) {
-    $variables['options']['attributes']['hreflang'] = $variables['options']['language']->getId();
-  }
-
-  // Set the "active" class if the 'set_active_class' option is not empty.
-  if (!empty($variables['options']['set_active_class'])) {
-    // Add a "data-drupal-link-query" attribute to let the drupal.active-link
-    // library know the query in a standardized manner.
-    if (!empty($variables['options']['query'])) {
-      $query = $variables['options']['query'];
-      ksort($query);
-      $variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query);
-    }
-
-    // Add a "data-drupal-link-system-path" attribute to let the
-    // drupal.active-link library know the path in a standardized manner.
-    if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
-      $variables['options']['attributes']['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager')->getPathByAlias($path);
-    }
-  }
-
-  // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
-  // only when a quick strpos() gives suspicion tags are present.
-  if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) {
-    $variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']);
-  }
-
-  // Allow other modules to modify the structure of the link.
-  \Drupal::moduleHandler()->alter('link', $variables);
-
-  // Move attributes out of options. url() doesn't need them.
-  $attributes = new Attribute($variables['options']['attributes']);
-  unset($variables['options']['attributes']);
-
-  // The result of url() is a plain-text URL. Because we are using it here
-  // in an HTML argument context, we need to encode it properly.
-  $url = String::checkPlain(_url($variables['path'], $variables['options']));
-
-  // Sanitize the link text if necessary.
-  $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
-  return SafeMarkup::set('<a href="' . $url . '"' . $attributes . '>' . $text . '</a>');
-}
-
-/**
  * Attempts to set the PHP maximum execution time.
  *
  * This function is a wrapper around the PHP function set_time_limit().
diff --git a/core/modules/aggregator/src/Plugin/views/field/TitleLink.php b/core/modules/aggregator/src/Plugin/views/field/TitleLink.php
index 4fb924f..fa56ab2 100644
--- a/core/modules/aggregator/src/Plugin/views/field/TitleLink.php
+++ b/core/modules/aggregator/src/Plugin/views/field/TitleLink.php
@@ -8,6 +8,7 @@
 namespace Drupal\aggregator\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
 use Drupal\views\ResultRow;
@@ -78,7 +79,8 @@ protected function renderLink($data, ResultRow $values) {
     $link = $this->getValue($values, 'link');
     if (!empty($this->options['display_as_link'])) {
       $this->options['alter']['make_link'] = TRUE;
-      $this->options['alter']['path'] = $link;
+      // Note: $link is an external URI, pointing to the feed itself.
+      $this->options['alter']['url'] = Url::fromUri($link);
       $this->options['alter']['html'] = TRUE;
       $this->options['alter']['absolute'] = TRUE;
     }
diff --git a/core/modules/comment/src/Plugin/views/field/Comment.php b/core/modules/comment/src/Plugin/views/field/Comment.php
index d804b13..31fa434 100644
--- a/core/modules/comment/src/Plugin/views/field/Comment.php
+++ b/core/modules/comment/src/Plugin/views/field/Comment.php
@@ -8,6 +8,7 @@
 namespace Drupal\comment\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
@@ -92,7 +93,7 @@ protected function renderLink($data, ResultRow $values) {
       $this->options['alter']['make_link'] = TRUE;
       $cid = $this->getValue($values, 'cid');
       if (!empty($cid)) {
-        $this->options['alter']['path'] = "comment/" . $cid;
+        $this->options['alter']['url'] = Url::fromRoute('entity.comment.canonical', ['comment' => $cid]);
         $this->options['alter']['fragment'] = "comment-" . $cid;
       }
       // If there is no comment link to the entity.
@@ -100,7 +101,7 @@ protected function renderLink($data, ResultRow $values) {
         $entity_id = $this->getValue($values, 'entity_id');
         $entity_type = $this->getValue($values, 'entity_type');
         $entity = entity_load($entity_type, $entity_id);
-        $this->options['alter']['path'] = $entity->getSystemPath();
+        $this->options['alter']['url'] = $entity->urlInfo();
       }
     }
 
diff --git a/core/modules/comment/src/Plugin/views/field/Link.php b/core/modules/comment/src/Plugin/views/field/Link.php
index cd5506d..adfaaa3 100644
--- a/core/modules/comment/src/Plugin/views/field/Link.php
+++ b/core/modules/comment/src/Plugin/views/field/Link.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -111,13 +112,13 @@ protected function renderLink($data, ResultRow $values) {
     $this->options['alter']['html'] = TRUE;
 
     if (!empty($cid)) {
-      $this->options['alter']['path'] = "comment/" . $cid;
+      $this->options['alter']['url'] = Url::fromRoute('entity.comment.canonical', ['comment' => $cid]);
       $this->options['alter']['fragment'] = "comment-" . $cid;
     }
     // If there is no comment link to the node.
     elseif ($this->options['link_to_node']) {
       $entity = $comment->getCommentedEntity();
-      $this->options['alter']['path'] = $entity->getSystemPath();
+      $this->options['alter']['url'] = $entity->urlInfo();
     }
 
     return $text;
diff --git a/core/modules/comment/src/Plugin/views/field/LinkApprove.php b/core/modules/comment/src/Plugin/views/field/LinkApprove.php
index 095eacd..b249a74 100644
--- a/core/modules/comment/src/Plugin/views/field/LinkApprove.php
+++ b/core/modules/comment/src/Plugin/views/field/LinkApprove.php
@@ -9,6 +9,7 @@
 
 use Drupal\comment\CommentInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 
 /**
@@ -51,8 +52,8 @@ protected function renderLink($data, ResultRow $values) {
     $comment = $this->get_entity($values);
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = "comment/" . $comment->id() . "/approve";
-    $this->options['alter']['query'] = drupal_get_destination() + array('token' => \Drupal::csrfToken()->get($this->options['alter']['path']));
+    $this->options['alter']['url'] = Url::fromRoute('comment.approve', ['comment' => $comment->id()]);
+    $this->options['alter']['query'] = drupal_get_destination() + array('token' => \Drupal::csrfToken()->get($this->options['alter']['url']->toString()));
 
     return $text;
   }
diff --git a/core/modules/comment/src/Plugin/views/field/LinkDelete.php b/core/modules/comment/src/Plugin/views/field/LinkDelete.php
index f22563b..b36c223 100644
--- a/core/modules/comment/src/Plugin/views/field/LinkDelete.php
+++ b/core/modules/comment/src/Plugin/views/field/LinkDelete.php
@@ -43,7 +43,7 @@ protected function renderLink($data, ResultRow $values) {
     $comment = $this->getEntity($values);
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = $comment->getSystemPath('delete-form');
+    $this->options['alter']['url'] = $comment->urlInfo('delete-form');
     $this->options['alter']['query'] = drupal_get_destination();
 
     return $text;
diff --git a/core/modules/comment/src/Plugin/views/field/LinkEdit.php b/core/modules/comment/src/Plugin/views/field/LinkEdit.php
index 4e687a3..316c272 100644
--- a/core/modules/comment/src/Plugin/views/field/LinkEdit.php
+++ b/core/modules/comment/src/Plugin/views/field/LinkEdit.php
@@ -63,7 +63,7 @@ protected function renderLink($data, ResultRow $values) {
       $this->options['alter']['query'] = drupal_get_destination();
     }
 
-    $this->options['alter']['path'] = "comment/" . $comment->id() . "/edit";
+    $this->options['alter']['url'] = $comment->urlInfo('edit-form');
 
     return $text;
   }
diff --git a/core/modules/comment/src/Plugin/views/field/LinkReply.php b/core/modules/comment/src/Plugin/views/field/LinkReply.php
index d9f6914..65c7545 100644
--- a/core/modules/comment/src/Plugin/views/field/LinkReply.php
+++ b/core/modules/comment/src/Plugin/views/field/LinkReply.php
@@ -8,6 +8,7 @@
 namespace Drupal\comment\Plugin\views\field;
 
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 
 /**
@@ -43,8 +44,12 @@ protected function renderLink($data, ResultRow $values) {
     $comment = $this->getEntity($values);
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = "comment/reply/{$comment->getCommentedEntityTypeId()}/{$comment->getCommentedEntityId()}/{$comment->getFieldName()}/{$comment->id()}";
-
+    $this->options['alter']['url'] = Url::fromRoute('comment.reply', [
+      'entity_type' => $comment->getCommentedEntityTypeId(),
+      'entity' => $comment->getCommentedEntityId(),
+      'field_name' => $comment->getFieldName(),
+      'pid' => $comment->id(),
+    ]);
     return $text;
   }
 
diff --git a/core/modules/comment/src/Plugin/views/field/NodeNewComments.php b/core/modules/comment/src/Plugin/views/field/NodeNewComments.php
index a95eaec..40b8a59 100644
--- a/core/modules/comment/src/Plugin/views/field/NodeNewComments.php
+++ b/core/modules/comment/src/Plugin/views/field/NodeNewComments.php
@@ -167,7 +167,7 @@ protected function renderLink($data, ResultRow $values) {
       $page_number = \Drupal::entityManager()->getStorage('comment')
         ->getNewCommentPageNumber($this->getValue($values, 'comment_count'), $this->getValue($values), $node);
       $this->options['alter']['make_link'] = TRUE;
-      $this->options['alter']['path'] = 'node/' . $node->id();
+      $this->options['alter']['url'] = $node->urlInfo();
       $this->options['alter']['query'] = $page_number ? array('page' => $page_number) : NULL;
       $this->options['alter']['fragment'] = 'new';
     }
diff --git a/core/modules/contact/src/Plugin/views/field/ContactLink.php b/core/modules/contact/src/Plugin/views/field/ContactLink.php
index 118380d..f746e00 100644
--- a/core/modules/contact/src/Plugin/views/field/ContactLink.php
+++ b/core/modules/contact/src/Plugin/views/field/ContactLink.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Drupal\user\Plugin\views\field\Link;
 use Drupal\views\ResultRow;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -117,7 +118,7 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) {
     }
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = "user/{$entity->id()}/contact";
+    $this->options['alter']['url'] =  Url::fromRoute('entity.user.contact_form', ['user' => $entity->id()]);
 
     $title = $this->t('Contact %user', array('%user' => $entity->name->value));
     $this->options['alter']['attributes'] = array('title' => $title);
diff --git a/core/modules/content_translation/src/Plugin/views/field/TranslationLink.php b/core/modules/content_translation/src/Plugin/views/field/TranslationLink.php
index 3727fb7..473c3b4 100644
--- a/core/modules/content_translation/src/Plugin/views/field/TranslationLink.php
+++ b/core/modules/content_translation/src/Plugin/views/field/TranslationLink.php
@@ -65,7 +65,7 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) {
       $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('Translate');
 
       $this->options['alter']['make_link'] = TRUE;
-      $this->options['alter']['path'] = $entity->getSystemPath('drupal:content-translation-overview');
+      $this->options['alter']['url'] = $entity->urlInfo('drupal:content-translation-overview');
 
       return $text;
     }
diff --git a/core/modules/contextual/src/Plugin/views/field/ContextualLinks.php b/core/modules/contextual/src/Plugin/views/field/ContextualLinks.php
index 684e4b7..f489d73 100644
--- a/core/modules/contextual/src/Plugin/views/field/ContextualLinks.php
+++ b/core/modules/contextual/src/Plugin/views/field/ContextualLinks.php
@@ -11,6 +11,7 @@
 use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
 use Drupal\views\ResultRow;
 
@@ -96,6 +97,9 @@ public function render(ResultRow $values) {
       if (!empty($this->view->field[$field]->options['alter']['path'])) {
         $path = $this->view->field[$field]->options['alter']['path'];
       }
+      elseif (!empty($this->view->field[$field]->options['alter']['url']) && $this->view->field[$field]->options['alter']['url'] instanceof Url) {
+        $path = $this->view->field[$field]->options['alter']['url']->toString();
+      }
       if (!empty($title) && !empty($path)) {
         // Make sure that tokens are replaced for this paths as well.
         $tokens = $this->getRenderTokens(array());
diff --git a/core/modules/file/src/Plugin/views/field/File.php b/core/modules/file/src/Plugin/views/field/File.php
index 816cc7b..d562178 100644
--- a/core/modules/file/src/Plugin/views/field/File.php
+++ b/core/modules/file/src/Plugin/views/field/File.php
@@ -8,6 +8,7 @@
 namespace Drupal\file\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
diff --git a/core/modules/node/src/Plugin/views/field/Link.php b/core/modules/node/src/Plugin/views/field/Link.php
index d089889..04c2961 100644
--- a/core/modules/node/src/Plugin/views/field/Link.php
+++ b/core/modules/node/src/Plugin/views/field/Link.php
@@ -76,7 +76,7 @@ public function render(ResultRow $values) {
   protected function renderLink($node, ResultRow $values) {
     if ($node->access('view')) {
       $this->options['alter']['make_link'] = TRUE;
-      $this->options['alter']['path'] = 'node/' . $node->id();
+      $this->options['alter']['url'] = $node->urlInfo();
       $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('View');
       return $text;
     }
diff --git a/core/modules/node/src/Plugin/views/field/LinkDelete.php b/core/modules/node/src/Plugin/views/field/LinkDelete.php
index c5cf1f7..6bc5156 100644
--- a/core/modules/node/src/Plugin/views/field/LinkDelete.php
+++ b/core/modules/node/src/Plugin/views/field/LinkDelete.php
@@ -37,7 +37,7 @@ protected function renderLink($node, ResultRow $values) {
     }
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = $node->getSystemPath('delete-form');
+    $this->options['alter']['url'] = $node->urlInfo('delete-form');
     $this->options['alter']['query'] = drupal_get_destination();
 
     $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('Delete');
diff --git a/core/modules/node/src/Plugin/views/field/LinkEdit.php b/core/modules/node/src/Plugin/views/field/LinkEdit.php
index b59a389..eb8b974 100644
--- a/core/modules/node/src/Plugin/views/field/LinkEdit.php
+++ b/core/modules/node/src/Plugin/views/field/LinkEdit.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\node\Plugin\views\field;
 
+use Drupal\Core\Url;
 use Drupal\node\Plugin\views\field\Link;
 use Drupal\views\ResultRow;
 
@@ -37,7 +38,7 @@ protected function renderLink($node, ResultRow $values) {
     }
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = "node/" . $node->id() . "/edit";
+    $this->options['alter']['url'] = $node->urlInfo('edit-form');
     $this->options['alter']['query'] = drupal_get_destination();
 
     $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('Edit');
diff --git a/core/modules/node/src/Plugin/views/field/Node.php b/core/modules/node/src/Plugin/views/field/Node.php
index a62e0f2..ecf4e18 100644
--- a/core/modules/node/src/Plugin/views/field/Node.php
+++ b/core/modules/node/src/Plugin/views/field/Node.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
@@ -71,7 +72,7 @@ protected function renderLink($data, ResultRow $values) {
     if (!empty($this->options['link_to_node']) && !empty($this->additional_fields['nid'])) {
       if ($data !== NULL && $data !== '') {
         $this->options['alter']['make_link'] = TRUE;
-        $this->options['alter']['path'] = "node/" . $this->getValue($values, 'nid');
+        $this->options['alter']['url'] = Url::fromRoute('entity.node.canonical', ['node' => $this->getValue($values, 'nid')]);
         if (isset($this->aliases['langcode'])) {
           $languages = \Drupal::languageManager()->getLanguages();
           $langcode = $this->getValue($values, 'langcode');
diff --git a/core/modules/node/src/Plugin/views/field/Revision.php b/core/modules/node/src/Plugin/views/field/Revision.php
index dd5d443..7f99615 100644
--- a/core/modules/node/src/Plugin/views/field/Revision.php
+++ b/core/modules/node/src/Plugin/views/field/Revision.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
@@ -68,7 +69,7 @@ protected function renderLink($data, ResultRow $values) {
       $this->options['alter']['make_link'] = TRUE;
       $nid = $this->getValue($values, 'nid');
       $vid = $this->getValue($values, 'vid');
-      $this->options['alter']['path'] = "node/" . $nid . '/revisions/' . $vid . '/view';
+      $this->options['alter']['url'] = Url::fromRoute('node.revision_show', ['node' => $nid, 'node_revision' => $vid]);
     }
     else {
       return parent::renderLink($data, $values);
diff --git a/core/modules/node/src/Plugin/views/field/RevisionLink.php b/core/modules/node/src/Plugin/views/field/RevisionLink.php
index 0298a5d..569532f 100644
--- a/core/modules/node/src/Plugin/views/field/RevisionLink.php
+++ b/core/modules/node/src/Plugin/views/field/RevisionLink.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Plugin\views\field;
 
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Drupal\node\Plugin\views\field\Link;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\ResultRow;
@@ -56,13 +57,15 @@ protected function renderLink($data, ResultRow $values) {
     }
 
     // Current revision uses the node view path.
-    $path = 'node/' . $node->nid;
     if (!$node->isDefaultRevision()) {
-      $path .= "/revisions/$vid/view";
+      $url = Url::fromRoute('node.revision_show', ['node' => $node->nid, 'node_revision' => $vid]);
+    }
+    else {
+      $url = $node->urlInfo();
     }
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = $path;
+    $this->options['alter']['url'] = $url;
     $this->options['alter']['query'] = drupal_get_destination();
 
     return !empty($this->options['text']) ? $this->options['text'] : $this->t('View');
diff --git a/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php b/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php
index dfc6891..1d5a14b 100644
--- a/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php
+++ b/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Plugin\views\field;
 
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Drupal\node\Plugin\views\field\RevisionLink;
 use Drupal\views\ResultRow;
 
@@ -50,7 +51,7 @@ protected function renderLink($data, ResultRow $values) {
     }
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = 'node/' . $node->id() . "/revisions/$vid/delete";
+    $this->options['alter']['url'] = Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]);
     $this->options['alter']['query'] = drupal_get_destination();
 
     return !empty($this->options['text']) ? $this->options['text'] : $this->t('Delete');
diff --git a/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php b/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php
index 2ceb19f..17db8a8 100644
--- a/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php
+++ b/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Plugin\views\field;
 
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Drupal\node\Plugin\views\field\RevisionLink;
 use Drupal\views\ResultRow;
 
@@ -50,7 +51,7 @@ protected function renderLink($data, ResultRow $values) {
     }
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = 'node/' . $node->id() . "/revisions/$vid/revert";
+    $this->options['alter']['url'] = Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]);
     $this->options['alter']['query'] = drupal_get_destination();
 
     return !empty($this->options['text']) ? $this->options['text'] : $this->t('Revert');
diff --git a/core/modules/taxonomy/src/Plugin/views/field/Taxonomy.php b/core/modules/taxonomy/src/Plugin/views/field/Taxonomy.php
index 7618e22..903f174 100644
--- a/core/modules/taxonomy/src/Plugin/views/field/Taxonomy.php
+++ b/core/modules/taxonomy/src/Plugin/views/field/Taxonomy.php
@@ -81,7 +81,7 @@ protected function renderLink($data, ResultRow $values) {
 
     if (!empty($this->options['link_to_taxonomy']) && $term && $data !== NULL && $data !== '') {
       $this->options['alter']['make_link'] = TRUE;
-      $this->options['alter']['path'] = $term->getSystemPath();
+      $this->options['alter']['url'] = $term->urlInfo();
     }
 
     if (!empty($this->options['convert_spaces'])) {
diff --git a/core/modules/user/src/Plugin/views/field/Language.php b/core/modules/user/src/Plugin/views/field/Language.php
index bf5abaa..708747d 100644
--- a/core/modules/user/src/Plugin/views/field/Language.php
+++ b/core/modules/user/src/Plugin/views/field/Language.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\user\Plugin\views\field;
 
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 
 /**
@@ -26,7 +27,7 @@ protected function renderLink($data, ResultRow $values) {
       $uid = $this->getValue($values, 'uid');
       if ($this->view->getUser()->hasPermission('access user profiles') && $uid) {
         $this->options['alter']['make_link'] = TRUE;
-        $this->options['alter']['path'] = 'user/' . $uid;
+        $this->options['alter']['url'] = Url::fromRoute('entity.user.canonical', ['user' => $uid]);
       }
     }
     if (empty($data)) {
diff --git a/core/modules/user/src/Plugin/views/field/Link.php b/core/modules/user/src/Plugin/views/field/Link.php
index b62d80e..d6f28e9 100644
--- a/core/modules/user/src/Plugin/views/field/Link.php
+++ b/core/modules/user/src/Plugin/views/field/Link.php
@@ -90,7 +90,7 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) {
     $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('View');
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = $entity->getSystemPath();
+    $this->options['alter']['url'] = $entity->urlInfo();
 
     return $text;
   }
diff --git a/core/modules/user/src/Plugin/views/field/LinkCancel.php b/core/modules/user/src/Plugin/views/field/LinkCancel.php
index acef491..abdbec7 100644
--- a/core/modules/user/src/Plugin/views/field/LinkCancel.php
+++ b/core/modules/user/src/Plugin/views/field/LinkCancel.php
@@ -28,7 +28,7 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) {
 
       $text = !empty($this->options['text']) ? $this->options['text'] : $this->t('Cancel account');
 
-      $this->options['alter']['path'] = $entity->getSystemPath('cancel-form');
+      $this->options['alter']['url'] = $entity->urlInfo('cancel-form');
       $this->options['alter']['query'] = drupal_get_destination();
 
       return $text;
diff --git a/core/modules/user/src/Plugin/views/field/Mail.php b/core/modules/user/src/Plugin/views/field/Mail.php
index 55db278..e933c0d 100644
--- a/core/modules/user/src/Plugin/views/field/Mail.php
+++ b/core/modules/user/src/Plugin/views/field/Mail.php
@@ -8,6 +8,7 @@
 namespace Drupal\user\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\views\ResultRow;
 
 /**
diff --git a/core/modules/user/src/Plugin/views/field/User.php b/core/modules/user/src/Plugin/views/field/User.php
index 9b33e1e..72d22c6 100644
--- a/core/modules/user/src/Plugin/views/field/User.php
+++ b/core/modules/user/src/Plugin/views/field/User.php
@@ -66,7 +66,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
   protected function renderLink($data, ResultRow $values) {
     if (!empty($this->options['link_to_user']) && $this->view->getUser()->hasPermission('access user profiles') && ($entity = $this->getEntity($values)) && $data !== NULL && $data !== '') {
       $this->options['alter']['make_link'] = TRUE;
-      $this->options['alter']['path'] = $entity->getSystemPath();
+      $this->options['alter']['url'] = $entity->urlInfo();
     }
     return $data;
   }
diff --git a/core/modules/views/src/Plugin/views/PluginBase.php b/core/modules/views/src/Plugin/views/PluginBase.php
index d26a5ea..8cd191f 100644
--- a/core/modules/views/src/Plugin/views/PluginBase.php
+++ b/core/modules/views/src/Plugin/views/PluginBase.php
@@ -370,6 +370,7 @@ protected function viewsTokenReplace($text, $tokens) {
         '#template' => $text,
         '#context' => $twig_tokens,
       );
+
       return drupal_render($build);
     }
     else {
diff --git a/core/modules/views/src/Plugin/views/field/EntityLabel.php b/core/modules/views/src/Plugin/views/field/EntityLabel.php
index 8b39f56..58b9687 100644
--- a/core/modules/views/src/Plugin/views/field/EntityLabel.php
+++ b/core/modules/views/src/Plugin/views/field/EntityLabel.php
@@ -112,7 +112,7 @@ public function render(ResultRow $values) {
 
     if (!empty($this->options['link_to_entity'])) {
       $this->options['alter']['make_link'] = TRUE;
-      $this->options['alter']['path'] = $entity->getSystemPath();
+      $this->options['alter']['url'] = $entity->urlInfo();
     }
 
     return $this->sanitizeValue($entity->label());
diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
index d60fcba..66a2e5f 100644
--- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -1259,7 +1259,7 @@ public function renderText($alter) {
     }
     $this->last_render_text = $value;
 
-    if (!empty($alter['make_link']) && !empty($alter['path'])) {
+    if (!empty($alter['make_link']) && (!empty($alter['path']) || !empty($alter['url']))) {
       if (!isset($tokens)) {
         $tokens = $this->getRenderTokens($alter);
       }
@@ -1295,34 +1295,48 @@ public function renderTrimText($alter, $value) {
    * the user.
    */
   protected function renderAsLink($alter, $text, $tokens) {
-    $value = '';
-
-    if (!empty($alter['prefix'])) {
-      $value .= Xss::filterAdmin($this->viewsTokenReplace($alter['prefix'], $tokens));
-    }
-
     $options = array(
-      'html' => TRUE,
       'absolute' => !empty($alter['absolute']) ? TRUE : FALSE,
+      'alias' => FALSE,
+      'entity' => NULL,
+      'entity_type' => NULL,
+      'fragment' => NULL,
+      'html' => TRUE,
+      'language' => NULL,
+      'query' => [],
     );
 
     $alter += [
       'path' => NULL
     ];
 
-    // $path will be run through check_url() by _l() so we do not need to
-    // sanitize it ourselves.
     $path = $alter['path'];
+    if (empty($alter['url'])) {
+      if (!parse_url($path, PHP_URL_SCHEME)) {
+        $alter['url'] = CoreUrl::fromUri('user-path:/' . ltrim($path, '/'));
+      }
+      else {
+        $alter['url'] = CoreUrl::fromUri($path);
+      }
+    }
+
+    $options = $alter['url']->getOptions() + $options;
+
+    $path = $alter['url']->setOptions($options)->toUriString();
 
     // strip_tags() removes <front>, so check whether its different to front.
-    if ($path != '<front>') {
+    if ($path != 'route:<front>') {
+      // Unescape Twig delimiters that may have been escaped by the
+      // Url::toUriString() call above.
+      $path = str_replace(['%7B','%7D'], ['{','}'], $path);
+
       // Use strip tags as there should never be HTML in the path.
       // However, we need to preserve special characters like " that
       // were removed by String::checkPlain().
       $path = strip_tags(String::decodeEntities($this->viewsTokenReplace($path, $tokens)));
 
-      if (!empty($alter['path_case']) && $alter['path_case'] != 'none') {
-        $path = $this->caseTransform($path, $this->options['alter']['path_case']);
+      if (!empty($alter['path_case']) && $alter['path_case'] != 'none' && !$alter['url']->isRouted()) {
+        $path = str_replace($alter['path'], $this->caseTransform($alter['path'], $this->options['alter']['path_case']), $path);
       }
 
       if (!empty($alter['replace_spaces'])) {
@@ -1355,7 +1369,8 @@ protected function renderAsLink($alter, $text, $tokens) {
     if ($alter['external']) {
       if (!isset($url['scheme'])) {
         // There is no scheme, add the default 'http://' to the $path.
-        $path = "http://$path";
+        // Use the original $alter['path'] instead of the parsed version.
+        $path = "http://" . $alter['path'];
         // Reset the $url array to include the new scheme.
         $url = UrlHelper::parse($path);
       }
@@ -1403,6 +1418,7 @@ protected function renderAsLink($alter, $text, $tokens) {
       $options['attributes']['rel'] = $rel;
     }
 
+    // Not sure if this String::checkPlain() is needed here?
     $target = String::checkPlain(trim($this->viewsTokenReplace($alter['target'], $tokens)));
     if (!empty($target)) {
       $options['attributes']['target'] = $target;
@@ -1448,16 +1464,19 @@ protected function renderAsLink($alter, $text, $tokens) {
       $options['entity_type'] = $alter['entity_type'];
     }
 
-    // @todo Add proper support for url objects, see
-    //   https://www.drupal.org/node/2404603
-    //   This means for example taking into account the options.
-    if (isset($options['url']) && $options['url'] instanceof Url) {
-      $value .= $this->linkGenerator()->generate($text, $options['url']);
-    }
-    else {
-      $value .= _l($text, $path, $options);
+    // The path has been heavily processed above, so it should be used as-is.
+    $final_url = CoreUrl::fromUri($path, $options);
+
+    // Build the link based on our altered Url object, adding on the optional
+    // prefix and suffix
+    $value = '';
+
+    if (!empty($alter['prefix'])) {
+      $value .= Xss::filterAdmin($this->viewsTokenReplace($alter['prefix'], $tokens));
     }
 
+    $value .= $this->linkGenerator()->generate($text, $final_url);
+
     if (!empty($alter['suffix'])) {
       $value .= Xss::filterAdmin($this->viewsTokenReplace($alter['suffix'], $tokens));
     }
@@ -1474,7 +1493,7 @@ public function getRenderTokens($item) {
       $tokens = $this->view->build_info['substitutions'];
     }
     $count = 0;
-    foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
+    foreach ($this->displayHandler->getHandlers('argument') as $arg => $handler) {
       $token = '%' . ++$count;
       if (!isset($tokens[$token])) {
         $tokens[$token] = '';
@@ -1487,10 +1506,12 @@ public function getRenderTokens($item) {
     }
 
     // Get flattened set of tokens for any array depth in query parameters.
-    $tokens += $this->getTokenValuesRecursive($this->view->getRequest()->query->all());
+    if ($request = $this->view->getRequest()) {
+      $tokens += $this->getTokenValuesRecursive($request->query->all());
+    }
 
     // Now add replacements for our fields.
-    foreach ($this->view->display_handler->getHandlers('field') as $field => $handler) {
+    foreach ($this->displayHandler->getHandlers('field') as $field => $handler) {
       if (isset($handler->last_render)) {
         $tokens["{{ $field }}"] = $handler->last_render;
       }
@@ -1718,5 +1739,5 @@ protected function getRenderer() {
 }
 
 /**
- * @}
+ * @} End of "defgroup views_field_handlers".
  */
diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php
new file mode 100644
index 0000000..78d8325
--- /dev/null
+++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php
@@ -0,0 +1,447 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\views\Unit\Plugin\field\FieldPluginBaseTest.
+ */
+
+namespace Drupal\Tests\views\Unit\Plugin\field;
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\Url;
+use Drupal\Core\Utility\LinkGenerator;
+use Drupal\Core\Utility\LinkGeneratorInterface;
+use Drupal\Core\Utility\UnroutedUrlAssembler;
+use Drupal\Tests\UnitTestCase;
+use Drupal\views\Plugin\views\field\FieldPluginBase;
+use Drupal\views\ResultRow;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Routing\Route;
+
+/**
+ * @coversDefaultClass \Drupal\views\Plugin\views\field\FieldPluginBase
+ * @group views
+ */
+class FieldPluginBaseTest extends UnitTestCase {
+
+  /**
+   * The configuration of the plugin under test.
+   *
+   * @var array
+   */
+  protected $configuration = [];
+
+  /**
+   * The ID plugin of the plugin under test.
+   *
+   * @var string
+   */
+  protected $pluginId = 'field_test';
+
+  /**
+   * The definition of the plugin under test.
+   *
+   * @var array
+   */
+  protected $pluginDefinition = [];
+
+  /**
+   * Default configuration for URL output.
+   *
+   * @var array
+   */
+  protected $defaultUrlOptions = [
+    'absolute' => FALSE,
+    'alias' => FALSE,
+    'entity' => NULL,
+    'entity_type' => NULL,
+    'html' => TRUE,
+    'language' => NULL,
+    'query' => [],
+    'set_active_class' => FALSE,
+  ];
+
+  /**
+   * The mocked link generator.
+   *
+   * @var \Drupal\Core\Utility\LinkGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $linkGenerator;
+
+  /**
+   * The mocked view executable.
+   *
+   * @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $executable;
+
+  /**
+   * The mocked display plugin instance.
+   *
+   * @var \Drupal\views\Plugin\views\display\DisplayPluginBase|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $display;
+
+  /**
+   * The mocked url generator.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $urlGenerator;
+
+  /**
+   * The mocked path validator.
+   *
+   * @var \Drupal\Core\Path\PathValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $pathValidator;
+
+  /**
+   * The unrouted url assembler service.
+   *
+   * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $unroutedUrlAssembler;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The mocked path processor.
+   *
+   * @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $pathProcessor;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
+    $route_provider->expects($this->any())
+      ->method('getRouteByName')
+      ->with('test_route')
+      ->willReturn(new Route('/test-path'));
+
+    $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
+    $this->pathValidator = $this->getMock('Drupal\Core\Path\PathValidatorInterface');
+
+    $this->requestStack = new RequestStack();
+    $this->requestStack->push(new Request());
+
+    $this->unroutedUrlAssembler = $this->getMock('Drupal\Core\Utility\UnroutedUrlAssemblerInterface');
+    $this->linkGenerator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface');
+
+    $container_builder = new ContainerBuilder();
+    $container_builder->set('url_generator', $this->urlGenerator);
+    $container_builder->set('path.validator', $this->pathValidator);
+    $container_builder->set('unrouted_url_assembler', $this->unroutedUrlAssembler);
+    $container_builder->set('request_stack', $this->requestStack);
+    \Drupal::setContainer($container_builder);
+  }
+
+  /**
+   * Sets up the unrouted url assembler and the link generator.
+   */
+  protected function setUpUrlIntegrationServices() {
+    $config = $this->getMockBuilder('Drupal\Core\Config\ImmutableConfig')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $config_factory = $this->getMock('\Drupal\Core\Config\ConfigFactoryInterface');
+    $config_factory->expects($this->any())
+      ->method('get')
+      ->willReturn($config);
+
+    $this->pathProcessor = $this->getMock('Drupal\Core\PathProcessor\OutboundPathProcessorInterface');
+    $this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $config_factory, $this->pathProcessor);
+
+    \Drupal::getContainer()->set('unrouted_url_assembler', $this->unroutedUrlAssembler);
+
+    $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'));
+  }
+
+  /**
+   * Sets up a display with empty arguments and fields.
+   */
+  protected function setupDisplayWithEmptyArgumentsAndFields() {
+    $this->display->expects($this->any())
+      ->method('getHandlers')
+      ->willReturnMap([
+        ['argument', []],
+        ['field', []],
+      ]);
+  }
+
+  /**
+   * Test rendering as a link without a path.
+   *
+   * @covers ::renderAsLink
+   */
+  public function testRenderAsLinkWithoutPath() {
+    $alter = [
+      'make_link' => TRUE,
+    ];
+
+    $this->setUpUrlIntegrationServices();
+    $field = $this->setupTestField(['alter' => $alter]);
+    $field->field_alias = 'key';
+    $row = new ResultRow(['key' => 'value']);
+
+    $expected_result = 'value';
+    $result = $field->advancedRender($row);
+    $this->assertEquals($expected_result, $result);
+  }
+
+  /**
+   * Test rendering of a link with a path and options.
+   *
+   * @dataProvider providerTestRenderAsLinkWithPathAndOptions
+   * @covers ::renderAsLink
+   */
+  public function testRenderAsLinkWithPathAndOptions($path, $alter, $link_html, $final_html = NULL) {
+    $alter += [
+      'make_link' => TRUE,
+      'path' => $path,
+    ];
+
+    $final_html = isset($final_html) ? $final_html : $link_html;
+
+    $this->setUpUrlIntegrationServices();
+    $this->setupDisplayWithEmptyArgumentsAndFields();
+    $field = $this->setupTestField(['alter' => $alter]);
+    $field->field_alias = 'key';
+    $row = new ResultRow(['key' => 'value']);
+
+    $result = $field->advancedRender($row);
+    $this->assertEquals($final_html, $result);
+  }
+
+  /**
+   * Data provider for ::testRenderAsLinkWithPathAndOptions().
+   *
+   * @return array
+   *   Test data.
+   */
+  public function providerTestRenderAsLinkWithPathAndOptions() {
+    $data = [];
+    // Simple path with default options.
+    $data[] = ['test-path', [], [], '<a href="/test-path">value</a>'];
+    // Add a fragment.
+    $data[] = ['test-path', ['fragment' => 'test'], '<a href="/test-path#test">value</a>'];
+    // Rel attributes.
+    $data[] = ['test-path', ['rel' => 'up'], '<a href="/test-path" rel="up">value</a>'];
+    // Target attributes.
+    $data[] = ['test-path', ['target' => '_blank'], '<a href="/test-path" target="_blank">value</a>'];
+    // Link attributes.
+    $data[] = ['test-path', ['link_attributes' => ['foo' => 'bar']], '<a href="/test-path" foo="bar">value</a>'];
+    // Manual specified query.
+    $data[] = ['test-path', ['query' => ['foo' => 'bar']], '<a href="/test-path?foo=bar">value</a>'];
+    // Query specified as part of the path.
+    $data[] = ['test-path?foo=bar', [], '<a href="/test-path?foo=bar">value</a>'];
+    // Query specified as option and path.
+    // @todo Do we expect that options override all existing ones?
+    $data[] = ['test-path?foo=bar', ['query' => ['key' => 'value']], '<a href="/test-path?key=value">value</a>'];
+    // Alias flag.
+    $data[] = ['test-path', ['alias' => TRUE], '<a href="/test-path">value</a>'];
+    // Note: In contrast to the testRenderAsLinkWithUrlAndOptions test we don't
+    // test the language, because the path processor for the language won't be
+    // executed for paths which aren't routed.
+
+    // Entity flag.
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $data[] = ['test-path', ['entity' => $entity], '<a href="/test-path">value</a>'];
+    // entity_type flag.
+    $entity_type_id = 'node';
+    $data[] = ['test-path', ['entity_type' => $entity_type_id], '<a href="/test-path">value</a>'];
+    // prefix
+    $data[] = ['test-path', ['prefix' => 'test_prefix'], '<a href="/test-path">value</a>', 'test_prefix<a href="/test-path">value</a>'];
+    // suffix.
+    $data[] = ['test-path', ['suffix' => 'test_suffix'], '<a href="/test-path">value</a>', '<a href="/test-path">value</a>test_suffix'];
+
+    // External URL.
+    $data[] = ['https://www.drupal.org', [], [], '<a href="https://www.drupal.org">value</a>'];
+
+    return $data;
+  }
+
+  /**
+   * Tests link rendering with a URL and options.
+   *
+   * @dataProvider providerTestRenderAsLinkWithUrlAndOptions
+   * @covers ::renderAsLink
+   */
+  public function testRenderAsLinkWithUrlAndOptions(Url $url, $alter, Url $expected_url, $url_path, Url $expected_link_url, $link_html, $final_html = NULL) {
+    $alter += [
+      'make_link' => TRUE,
+      'url' => $url,
+    ];
+
+    $final_html = isset($final_html) ? $final_html : $link_html;
+
+    $this->setUpUrlIntegrationServices();
+    $this->setupDisplayWithEmptyArgumentsAndFields();
+    $field = $this->setupTestField(['alter' => $alter]);
+    $field->field_alias = 'key';
+    $row = new ResultRow(['key' => 'value']);
+
+    $expected_url->setOptions($expected_url->getOptions() + $this->defaultUrlOptions);
+    $expected_link_url->setUrlGenerator($this->urlGenerator);
+
+    $expected_url_options = $expected_url->getOptions();
+    unset($expected_url_options['attributes']);
+
+    $this->urlGenerator->expects($this->once())
+      ->method('generateFromRoute')
+      ->with($expected_url->getRouteName(), $expected_url->getRouteParameters(), $expected_url_options)
+      ->willReturn($url_path);
+
+    $result = $field->advancedRender($row);
+    $this->assertEquals($final_html, $result);
+  }
+
+  /**
+   * Data provider for ::testRenderAsLinkWithUrlAndOptions().
+   *
+   * @return array
+   *   Array of test data.
+   */
+  public function providerTestRenderAsLinkWithUrlAndOptions() {
+    $data = [];
+
+    // Simple path with default options.
+    $url = Url::fromRoute('test_route');
+    $data[]= [$url, [], clone $url, '/test-path', clone $url, '<a href="/test-path">value</a>'];
+
+    // Simple url with parameters.
+    $url_parameters = Url::fromRoute('test_route', ['key' => 'value']);
+    $data[]= [$url_parameters, [], clone $url_parameters, '/test-path/value', clone $url_parameters, '<a href="/test-path/value">value</a>'];
+
+    // Add a fragment.
+    $url = Url::fromRoute('test_route');
+    $url_with_fragment = Url::fromRoute('test_route');
+    $options = ['fragment' => 'test'] + $this->defaultUrlOptions;
+    $url_with_fragment->setOptions($options);
+    $data[]= [$url, ['fragment' => 'test'], $url_with_fragment, '/test-path#test', clone $url_with_fragment, '<a href="/test-path#test">value</a>'];
+
+    // Rel attributes.
+    $url = Url::fromRoute('test_route');
+    $url_with_rel = Url::fromRoute('test_route');
+    $options = ['attributes' => ['rel' => 'up']] + $this->defaultUrlOptions;
+    $url_with_rel->setOptions($options);
+    $data[]= [$url, ['rel' => 'up'], clone $url, '/test-path', $url_with_rel, '<a href="/test-path" rel="up">value</a>'];
+
+    // Target attributes.
+    $url = Url::fromRoute('test_route');
+    $url_with_target = Url::fromRoute('test_route');
+    $options = ['attributes' => ['target' => '_blank']] + $this->defaultUrlOptions;
+    $url_with_target->setOptions($options);
+    $data[]= [$url, ['target' => '_blank'], $url_with_target, '/test-path', clone $url_with_target, '<a href="/test-path" target="_blank">value</a>'];
+
+    // Link attributes.
+    $url = Url::fromRoute('test_route');
+    $url_with_link_attributes = Url::fromRoute('test_route');
+    $options = ['attributes' => ['foo' => 'bar']] + $this->defaultUrlOptions;
+    $url_with_link_attributes->setOptions($options);
+    $data[]= [$url, ['link_attributes' => ['foo' => 'bar']], clone $url, '/test-path', $url_with_link_attributes, '<a href="/test-path" foo="bar">value</a>'];
+
+    // Manual specified query.
+    $url = Url::fromRoute('test_route');
+    $url_with_query = Url::fromRoute('test_route');
+    $options = ['query' => ['foo' => 'bar']] + $this->defaultUrlOptions;
+    $url_with_query->setOptions($options);
+    $data[]= [$url, ['query' => ['foo' => 'bar']], clone $url_with_query, '/test-path?foo=bar', $url_with_query, '<a href="/test-path?foo=bar">value</a>'];
+
+    // Query specified as part of the path.
+    $url = Url::fromRoute('test_route')->setOption('query', ['foo' => 'bar']);
+    $url_with_query = clone $url;
+    $url_with_query->setOptions(['query' => ['foo' => 'bar']] + $url_with_query->getOptions());
+    $data[] = [$url, [], $url_with_query, '/test-path?foo=bar', clone $url, '<a href="/test-path?foo=bar">value</a>'];
+
+    // Query specified as option and path.
+    $url = Url::fromRoute('test_route')->setOption('query', ['foo' => 'bar']);
+    $url_with_query = Url::fromRoute('test_route');
+    $options = ['query' => ['key' => 'value']] + $this->defaultUrlOptions;
+    $url_with_query->setOptions($options);
+    $data[] = [$url, ['query' => ['key' => 'value']], $url_with_query, '/test-path?key=value', clone $url_with_query, '<a href="/test-path?key=value">value</a>'];
+
+    // Alias flag.
+    $url = Url::fromRoute('test_route');
+    $url_without_alias = Url::fromRoute('test_route');
+    $options = ['alias' => TRUE] + $this->defaultUrlOptions;
+    $url_without_alias->setOptions($options);
+    $data[] = [$url, ['alias' => TRUE], $url_without_alias, '/test-path', clone $url_without_alias, '<a href="/test-path">value</a>'];
+
+    // Language flag.
+    $language = new Language(['id' => 'fr']);
+    $url = Url::fromRoute('test_route');
+    $url_with_language = Url::fromRoute('test_route');
+    $options = ['language' => $language] + $this->defaultUrlOptions;
+    $url_with_language->setOptions($options);
+    $data[] = [$url, ['language' => $language], $url_with_language, '/fr/test-path', clone $url_with_language, '<a href="/fr/test-path" hreflang="fr">value</a>'];
+
+    // Entity flag.
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $url = Url::fromRoute('test_route');
+    $url_with_entity = Url::fromRoute('test_route');
+    $options = ['entity' => $entity] + $this->defaultUrlOptions;
+    $url_with_entity->setOptions($options);
+    $data[] = [$url, ['entity' => $entity], $url_with_entity, '/test-path', clone $url_with_entity, '<a href="/test-path">value</a>'];
+
+    // Test entity_type flag.
+    $entity_type_id = 'node';
+    $url = Url::fromRoute('test_route');
+    $url_with_entity_type = Url::fromRoute('test_route');
+    $options = ['entity_type' => $entity_type_id] + $this->defaultUrlOptions;
+    $url_with_entity_type->setOptions($options);
+    $data[] = [$url, ['entity_type' => $entity_type_id], $url_with_entity_type, '/test-path', clone $url_with_entity_type, '<a href="/test-path">value</a>'];
+
+    // Test prefix.
+    $url = Url::fromRoute('test_route');
+    $data[] = [$url, ['prefix' => 'test_prefix'], clone $url, '/test-path', clone $url, '<a href="/test-path">value</a>', 'test_prefix<a href="/test-path">value</a>'];
+
+    // Test suffix.
+    $url = Url::fromRoute('test_route');
+    $data[] = [$url, ['suffix' => 'test_suffix'], clone $url, '/test-path', clone $url, '<a href="/test-path">value</a>', '<a href="/test-path">value</a>test_suffix'];
+
+    return $data;
+  }
+
+  /**
+   * Sets up a test field.
+   *
+   * @return \Drupal\Tests\views\Unit\Plugin\field\TestField|\PHPUnit_Framework_MockObject_MockObject
+   *   The test field.
+   */
+  protected function setupTestField(array $options = []) {
+    /** @var \Drupal\Tests\views\Unit\Plugin\field\TestField $field */
+    $field = $this->getMock('Drupal\Tests\views\Unit\Plugin\field\TestField', ['l'], [$this->configuration, $this->pluginId, $this->pluginDefinition]);
+    $field->init($this->executable, $this->display, $options);
+    $field->setLinkGenerator($this->linkGenerator);
+
+    return $field;
+  }
+
+}
+
+class TestField extends FieldPluginBase {
+
+  public function setLinkGenerator(LinkGeneratorInterface $link_generator) {
+    $this->linkGenerator = $link_generator;
+  }
+
+}
