Merge "Remove `globals` from eslintrc.js"
diff --git a/polygerrit-ui/app/api/plugin.ts b/polygerrit-ui/app/api/plugin.ts
index 4aa3aaa..684429a 100644
--- a/polygerrit-ui/app/api/plugin.ts
+++ b/polygerrit-ui/app/api/plugin.ts
@@ -15,6 +15,7 @@
 import {RestPluginApi} from './rest';
 import {HookApi, RegisterOptions} from './hook';
 import {StylePluginApi} from './styles';
+import {SuggestionsPluginApi} from './suggestions';
 
 export enum TargetElement {
   CHANGE_ACTIONS = 'changeactions',
@@ -58,6 +59,7 @@
   changeActions(): ChangeActionsPluginApi;
   changeReply(): ChangeReplyPluginApi;
   checks(): ChecksPluginApi;
+  suggestions(): SuggestionsPluginApi;
   eventHelper(element: Node): EventHelperPluginApi;
   getPluginName(): string;
   hook<T extends HTMLElement>(
diff --git a/polygerrit-ui/app/api/suggestions.ts b/polygerrit-ui/app/api/suggestions.ts
index f75f6e0..4038102 100644
--- a/polygerrit-ui/app/api/suggestions.ts
+++ b/polygerrit-ui/app/api/suggestions.ts
@@ -19,7 +19,7 @@
   patchsetNumber: RevisionPatchSetNum;
   filePath: string;
   range?: CommentRange;
-  lineNumber?: Number;
+  lineNumber?: number;
 }
 
 export declare interface SuggestionsProvider {
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
index 9bd56be..90b05f6 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-change.ts
@@ -3,7 +3,7 @@
  * Copyright 2021 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import {LitElement, css, html, nothing} from 'lit';
+import {LitElement, css, html} from 'lit';
 import {customElement, property} from 'lit/decorators.js';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {
@@ -87,11 +87,10 @@
         .submittableCheck {
           padding-left: var(--spacing-s);
           color: var(--positive-green-text-color);
-          display: inline;
-          width: 20px;
+          display: none;
         }
-        .submittableCheck.submittable:after {
-          content: '✓';
+        .submittableCheck.submittable {
+          display: inline;
         }
       `,
     ];
@@ -103,14 +102,22 @@
     const linkClass = this.computeLinkClass(change);
     return html`
       <div class="changeContainer">
-        ${this.showSubmittableCheck ? this.renderSubmittableCheck() : nothing}
         <a
           href=${ifDefined(this.href)}
           aria-label=${ifDefined(this.label)}
           class=${linkClass}
           ><slot></slot
         ></a>
-
+        ${this.showSubmittableCheck
+          ? html`<span
+              tabindex="-1"
+              title="Submittable"
+              class="submittableCheck ${linkClass}"
+              role="img"
+              aria-label="Submittable"
+              >✓</span
+            >`
+          : ''}
         ${this.showChangeStatus
           ? html`<span class=${this.computeChangeStatusClass(change)}>
               (${this.computeChangeStatus(change)})
@@ -120,25 +127,6 @@
     `;
   }
 
-  private renderSubmittableCheck() {
-    if (this.change?.submittable) {
-      return html`<span
-        tabindex="-1"
-        title="Submittable"
-        class="submittableCheck submittable"
-        role="img"
-        aria-label="Submittable"
-      ></span>`;
-    } else {
-      // Empty place-holder to ensure that columns line up.
-      return html`<span
-        tabindex="-1"
-        class="submittableCheck"
-        role="img"
-      ></span>`;
-    }
-  }
-
   private computeLinkClass(change: ChangeInfo | RelatedChangeAndCommitInfo) {
     const statuses = [];
     if (change.status === ChangeStatus.ABANDONED) {
diff --git a/polygerrit-ui/app/elements/plugins/gr-suggestions-api/gr-suggestions-api.ts b/polygerrit-ui/app/elements/plugins/gr-suggestions-api/gr-suggestions-api.ts
new file mode 100644
index 0000000..4006849
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-suggestions-api/gr-suggestions-api.ts
@@ -0,0 +1,48 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import {PluginApi} from '../../../api/plugin';
+import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
+import {PluginsModel} from '../../../models/plugins/plugins-model';
+import {
+  SuggestionsPluginApi,
+  SuggestionsProvider,
+} from '../../../api/suggestions';
+
+enum State {
+  NOT_REGISTERED,
+  REGISTERED,
+}
+
+/**
+ * Plugin API for suggestions.
+ *
+ * This object is returned to plugins that want to provide suggestions data.
+ * Plugins normally just call register() once at startup and then wait for
+ * suggestCode() being called on the provider interface.
+ */
+export class GrSuggestionsApi implements SuggestionsPluginApi {
+  private state = State.NOT_REGISTERED;
+
+  constructor(
+    private readonly reporting: ReportingService,
+    private readonly pluginsModel: PluginsModel,
+    readonly plugin: PluginApi
+  ) {
+    this.reporting.trackApi(this.plugin, 'suggestions', 'constructor');
+  }
+
+  register(provider: SuggestionsProvider): void {
+    this.reporting.trackApi(this.plugin, 'suggestions', 'register');
+    if (this.state === State.REGISTERED) {
+      throw new Error('Only one provider can be registered per plugin.');
+    }
+    this.state = State.REGISTERED;
+    this.pluginsModel.suggestionsRegister({
+      pluginName: this.plugin.getPluginName(),
+      provider,
+    });
+  }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index e2229b5..6ca4702 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -889,7 +889,10 @@
     });
     const replacement = suggestion.suggestions?.[0].replacement;
     if (!replacement) return;
-    this.messageText += `${USER_SUGGESTION_START_PATTERN}${suggestion}${'\n```'}`;
+    const addNewLine = this.messageText.length !== 0;
+    this.messageText += `${
+      addNewLine ? '\n' : ''
+    }${'```\n'}${replacement}${'\n```'}`;
   }
 
   private renderRobotActions() {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
index 832b97e..d3dea78 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.ts
@@ -37,6 +37,7 @@
 import {PluginsModel} from '../../../models/plugins/plugins-model';
 import {GrPluginStyleApi} from './gr-plugin-style-api';
 import {StylePluginApi} from '../../../api/styles';
+import {GrSuggestionsApi} from '../../plugins/gr-suggestions-api/gr-suggestions-api';
 
 const PLUGIN_NAME_NOT_SET = 'NULL';
 
@@ -214,6 +215,10 @@
     return new GrChecksApi(this.report, this.pluginsModel, this);
   }
 
+  suggestions(): GrSuggestionsApi {
+    return new GrSuggestionsApi(this.report, this.pluginsModel, this);
+  }
+
   reporting(): ReportingPluginApi {
     return new GrReportingJsApi(this.report, this);
   }