From d7fbabd8a54e02b49e50dacb52a67875763e0183 Mon Sep 17 00:00:00 2001
From: Katie Gengler
Date: Thu, 6 Aug 2026 18:11:37 -0400
Subject: [PATCH] Update generation of import examples for static functions
Previously, all import examples for static functions relied upon there
being a legacy mapping for the API in mappings.json. This meant that
newly added APIs would not display import examples.
Deciding whether to generate an import example now relies on whether the
package contains an `@` or if the package is `rsvp` -- the sole non-@
package that displays import examples today. This strikes a balance of
restoring, even on slightly older versions, the import examples that
should have been there while maintaining no examples on very old (<2.18)
versions.
Additionally added support for @noimport tag in doc blocks to indicate
APIs that do no have imports (aka built-in keywords for use in
templates).
Also added @exampleimport for doc blocks so that import paths can be
overridden. Right now the example import generation expects imports
to always be import { name } from 'module'; but there are cases where
this may not be true so the ability to override and have them specified
in the codebase is preferable.
---
app/components/class-field-description.gjs | 40 ++++----
app/components/import-example.gjs | 11 ++-
.../class-field-description-test.gjs | 92 +++++++++++++++++++
.../components/import-example-test.gjs | 16 ++++
4 files changed, 138 insertions(+), 21 deletions(-)
diff --git a/app/components/class-field-description.gjs b/app/components/class-field-description.gjs
index d99c2b67..436d298f 100644
--- a/app/components/class-field-description.gjs
+++ b/app/components/class-field-description.gjs
@@ -1,11 +1,8 @@
-import { service } from '@ember/service';
import Component from '@glimmer/component';
import svgJar from 'ember-svg-jar/helpers/svg-jar';
import { LinkTo } from '@ember/routing';
import { array, concat } from '@ember/helper';
import githubLink from 'ember-api-docs/helpers/github-link';
-import and from 'ember-truth-helpers/helpers/and';
-import eq from 'ember-api-docs/helpers/eq';
import ImportExample from 'ember-api-docs/components/import-example';
import MarkdownToHtml from 'ember-cli-showdown/components/markdown-to-html';
@@ -19,6 +16,16 @@ function combineNames(items) {
}
export default class ClassFieldDescription extends Component {
+ get displayImportExample() {
+ return (
+ this.args.field.exampleimport !== undefined ||
+ (this.args.field.noimport === undefined &&
+ this.args.field.static === 1 &&
+ this.args.field.itemtype === 'method' &&
+ isImportablePackage(this.args.field.class))
+ );
+ }
+
{{! template-lint-disable no-invalid-interactive }}
@@ -101,16 +108,11 @@ export default class ClassFieldDescription extends Component {
Available since v{{@field.since}}
{{/if}}
- {{#if
- (and
- (eq @field.static 1)
- (eq @field.itemtype "method")
- this.hasImportExample
- )
- }}
+ {{#if this.displayImportExample}}
{{/if}}
@@ -143,13 +145,15 @@ export default class ClassFieldDescription extends Component {
- @service
- legacyModuleMappings;
+}
- get hasImportExample() {
- return this.legacyModuleMappings.hasFunctionMapping(
- this.args.field.name,
- this.args.field.class,
- );
- }
+function isImportablePackage(packageName) {
+ /* Broadly define which types of static functions display generated import
+ examples. This previously relied on `mappings.json` but this meant that newly
+ added APIs did not display import examples.
+
+ This should strike a balance of displaying import examples for new APIs and
+ not displaying them before the APIs were importable.
+ */
+ return packageName.startsWith('@') || packageName === 'rsvp';
}
diff --git a/app/components/import-example.gjs b/app/components/import-example.gjs
index 3a528dc1..93797843 100644
--- a/app/components/import-example.gjs
+++ b/app/components/import-example.gjs
@@ -2,11 +2,16 @@ import Component from '@glimmer/component';
import MarkdownToHtml from 'ember-cli-showdown/components/markdown-to-html';
export default class ImportExample extends Component {
-
get markdown() {
- let md = `\`\`\`js
+ if (this.args.exampleimport) {
+ return `\`\`\`js
+${this.args.exampleimport}
+\`\`\``;
+ }
+ return `\`\`\`js
import ${this.args.item} from '${this.args.package}';
\`\`\``;
- return md;
}
+
+
}
diff --git a/tests/integration/components/class-field-description-test.gjs b/tests/integration/components/class-field-description-test.gjs
index d5700619..a70a9cbe 100644
--- a/tests/integration/components/class-field-description-test.gjs
+++ b/tests/integration/components/class-field-description-test.gjs
@@ -52,6 +52,98 @@ module('Integration | Component | class field description', function (hooks) {
assert.dom('[data-test-module]').hasText('@ember/component');
});
+ test('shows import example for static method on @-scoped package', async function (assert) {
+ const type = 'method';
+ const field = {
+ name: 'hash',
+ static: 1,
+ itemtype: 'method',
+ class: '@ember/helper',
+ };
+
+ await render(
+
+
+ ,
+ );
+
+ assert.dom('*').containsText("import { hash } from '@ember/helper';");
+ });
+
+ test('shows import example for static method on rsvp', async function (assert) {
+ const type = 'method';
+ const field = {
+ name: 'all',
+ static: 1,
+ itemtype: 'method',
+ class: 'rsvp',
+ };
+
+ await render(
+
+
+ ,
+ );
+
+ assert.dom('*').containsText("import { all } from 'rsvp';");
+ });
+
+ test('does not show import example when noimport flag exists', async function (assert) {
+ const type = 'method';
+ const field = {
+ name: 'hash',
+ static: 1,
+ itemtype: 'method',
+ class: '@ember/helper',
+ noimport: '',
+ };
+
+ await render(
+
+
+ ,
+ );
+
+ assert.dom('pre code').doesNotExist();
+ });
+
+ test('does not show import example for non-importable package', async function (assert) {
+ const type = 'method';
+ const field = {
+ name: 'someMethod',
+ static: 1,
+ itemtype: 'method',
+ class: 'SomeInternalClass',
+ };
+
+ await render(
+
+
+ ,
+ );
+
+ assert.dom('pre code').doesNotExist();
+ });
+
+ test('shows import example when field has exampleimport override', async function (assert) {
+ const type = 'method';
+ const field = {
+ name: 'myMethod',
+ exampleimport: "import { myMethod } from 'some-package/internal';",
+ class: 'SomeInternalClass',
+ };
+
+ await render(
+
+
+ ,
+ );
+
+ assert
+ .dom('*')
+ .containsText("import { myMethod } from 'some-package/internal';");
+ });
+
test('parameter props are displayed', async function (assert) {
const type = 'method';
const field = {
diff --git a/tests/integration/components/import-example-test.gjs b/tests/integration/components/import-example-test.gjs
index 1bdb73c2..2402c23c 100644
--- a/tests/integration/components/import-example-test.gjs
+++ b/tests/integration/components/import-example-test.gjs
@@ -25,4 +25,20 @@ module('Integration | Component | import example', function (hooks) {
);
assert.dom('*').hasText("import { uniqBy } from '@ember/object/computed';");
});
+
+ test('it renders a custom exampleimport override', async function (assert) {
+ await render(
+
+ {{! template-lint-disable no-potential-path-strings }}
+
+ ,
+ );
+ assert
+ .dom('*')
+ .hasText("import { someMethod } from '@ember/utils/legacy';");
+ });
});