Repository: JoshuaKGoldberg/create-typescript-app (ts)

Issue:
🚀 Feature: Don't include n/no-missing-import allowModules if README.md doesn't violate it
### Bug Report Checklist

- [X] I have pulled the latest `main` branch of the repository.
- [X] I have [searched for related issues](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue) and found none that matched my issue.

### Overview

Right now, the generated ESLint config for repos always configures `n/no-missing-import`'s `allowModules` to include `options.repository`:

https://github.com/JoshuaKGoldberg/create-typescript-app/blob/aaf2d6536de8a2ff862c132cad5e2351a9ebe923/src/steps/writing/creation/createESLintConfig.ts#L101-L106

This is necessary because created repos by default have a _Getting Started_ / _Usage_ section like:

```ts
import { greet } from "create-typescript-app";
```

But, in the `create` Blocks version, that's not always true. Repositories may customize that section. This repo does it and removes the offending code altogether.

So, I'd like to have the ESLint Block only include that `allowModules` setting if `options.usage` actually includes code that would make it useful.

### Additional Info

I don't plan on adding this to the legacy engine, just `create` blocks.

Prototyped in #1839.

💖 

Pull request description:
feat: don't add n/no-missing-import if usage doesn't need it
## PR Checklist

- [x] Addresses an existing open issue: fixes #1850
- [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22)
- [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/create-typescript-app/blob/main/.github/CONTRIBUTING.md) were taken

## Overview

💖 

Patch:
diff --git a/knip.json b/knip.json
index 011f1bc..57c4f15 100644
--- a/knip.json
+++ b/knip.json
@@ -1,7 +1,7 @@
 {
 	"$schema": "https://unpkg.com/knip@5.41.1/schema.json",
-	"entry": ["script/*e2e.js", "src/index.ts!", "src/**/*.test.*"],
+	"entry": ["script/*e2e.js", "src/index.ts", "src/**/*.test.*"],
 	"ignoreDependencies": ["all-contributors-cli", "cspell-populate-words"],
 	"ignoreExportsUsedInFile": { "interface": true, "type": true },
-	"project": ["src/**/*.ts!", "script/**/*.js"]
+	"project": ["src/**/*.ts", "script/**/*.js"]
 }
diff --git a/src/next/blocks/blockESLintNode.ts b/src/next/blocks/blockESLintNode.ts
index 19762c5..9a2fcbc 100644
--- a/src/next/blocks/blockESLintNode.ts
+++ b/src/next/blocks/blockESLintNode.ts
@@ -14,12 +14,14 @@ export const blockESLintNode = base.createBlock({
 						{
 							extends: ["tseslint.configs.disableTypeChecked"],
 							files: ["**/*.md/*.ts"],
-							rules: {
-								"n/no-missing-import": [
-									"error",
-									{ allowModules: [options.repository] },
-								],
-							},
+							...(options.usage?.includes(`from "${options.repository}`) && {
+								rules: {
+									"n/no-missing-import": [
+										"error",
+										{ allowModules: [options.repository] },
+									],
+								},
+							}),
 						},
 					],
 					imports: [{ source: "eslint-plugin-n", specifier: "n" }],
diff --git a/src/next/blocks/blockKnip.ts b/src/next/blocks/blockKnip.ts
index fadbb20..d727d5e 100644
--- a/src/next/blocks/blockKnip.ts
+++ b/src/next/blocks/blockKnip.ts
@@ -53,12 +53,12 @@ export const blockKnip = base.createBlock({
 			files: {
 				"knip.json": JSON.stringify({
 					$schema: `https://unpkg.com/knip@${getPackageDependency("knip")}/schema.json`,
-					entry: ["src/index.ts!"],
+					entry: ["src/index.ts"],
 					ignoreExportsUsedInFile: {
 						interface: true,
 						type: true,
 					},
-					project: ["src/**/*.ts!"],
+					project: ["src/**/*.ts"],
 				}),
 			},
 		};
diff --git a/src/next/blocks/blockMarkdownlint.ts b/src/next/blocks/blockMarkdownlint.ts
index 0f1cd90..c3e9a75 100644
--- a/src/next/blocks/blockMarkdownlint.ts
+++ b/src/next/blocks/blockMarkdownlint.ts
@@ -1,5 +1,6 @@
 import { z } from "zod";
 
+import { formatIgnoreFile } from "../../steps/writing/creation/formatters/formatIgnoreFile.js";
 import { base } from "../base.js";
 import { blockCSpell } from "./blockCSpell.js";
 import { blockDevelopmentDocs } from "./blockDevelopmentDocs.js";
@@ -66,14 +67,14 @@ export const blockMarkdownlint = base.createBlock({
 					"first-line-h1": false,
 					"no-inline-html": false,
 				}),
-				".markdownlintignore": [
-					".github/CODE_OF_CONDUCT.md",
-					"CHANGELOG.md",
-					"node_modules/",
-					...ignores,
-				]
-					.sort()
-					.join("\n"),
+				".markdownlintignore": formatIgnoreFile(
+					[
+						".github/CODE_OF_CONDUCT.md",
+						"CHANGELOG.md",
+						"node_modules/",
+						...ignores,
+					].sort(),
+				),
 			},
 			scripts: [
 				{
diff --git a/src/next/blocks/blockPrettier.ts b/src/next/blocks/blockPrettier.ts
index bce284e..628a556 100644
--- a/src/next/blocks/blockPrettier.ts
+++ b/src/next/blocks/blockPrettier.ts
@@ -1,5 +1,6 @@
 import { z } from "zod";
 
+import { formatIgnoreFile } from "../../steps/writing/creation/formatters/formatIgnoreFile.js";
 import { base } from "../base.js";
 import { blockCSpell } from "./blockCSpell.js";
 import { blockDevelopmentDocs } from "./blockDevelopmentDocs.js";
@@ -96,9 +97,9 @@ pnpm format --write
 					".gitignore": "_\n",
 					"pre-commit": ["npx lint-staged\n", { mode: 0x777 }],
 				},
-				".prettierignore": ["/.husky", "/lib", "/pnpm-lock.yaml", ...ignores]
-					.sort()
-					.join("\n"),
+				".prettierignore": formatIgnoreFile(
+					["/.husky", "/lib", "/pnpm-lock.yaml", ...ignores].sort(),
+				),
 				".prettierrc.json": JSON.stringify({
 					$schema: "http://json.schemastore.org/prettierrc",
 					...(overrides.length && { overrides: overrides.sort() }),
diff --git a/src/steps/writing/creation/createKnipConfig.ts b/src/steps/writing/creation/createKnipConfig.ts
index ba7d41a..08e98db 100644
--- a/src/steps/writing/creation/createKnipConfig.ts
+++ b/src/steps/writing/creation/createKnipConfig.ts
@@ -9,11 +9,11 @@ export async function createKnipConfig() {
 
 	return await formatJson({
 		$schema: `https://unpkg.com/knip@${existingPackageJson?.devDependencies?.knip ?? "latest"}/schema.json`,
-		entry: ["src/index.ts!"],
+		entry: ["src/index.ts"],
 		ignoreExportsUsedInFile: {
 			interface: true,
 			type: true,
 		},
-		project: ["src/**/*.ts!"],
+		project: ["src/**/*.ts"],
 	});
 }


Test patch:
diff --git a/script/__snapshots__/migrate-test-e2e.ts.snap b/script/__snapshots__/migrate-test-e2e.ts.snap
index 1dbbf8b..a4452a0 100644
--- a/script/__snapshots__/migrate-test-e2e.ts.snap
+++ b/script/__snapshots__/migrate-test-e2e.ts.snap
@@ -195,19 +195,6 @@ exports[`expected file changes > eslint.config.js 1`] = `
  			// Stylistic concerns that don't interfere with Prettier
  			"logical-assignment-operators": [
  				"error",
-@@ ... @@ export default tseslint.config(
- 	{
- 		extends: [tseslint.configs.disableTypeChecked],
- 		files: ["**/*.md/*.ts"],
-+		rules: {
-+			"n/no-missing-import": [
-+				"error",
-+				{ allowModules: ["create-typescript-app"] },
-+			],
-+		},
- 	},
- 	{
- 		extends: [vitest.configs.recommended],"
 `;
 
 exports[`expected file changes > knip.json 1`] = `
@@ -216,12 +203,12 @@ exports[`expected file changes > knip.json 1`] = `
 @@ ... @@
  {
  	"$schema": "https://unpkg.com/knip@5.41.1/schema.json",
--	"entry": ["script/*e2e.js", "src/index.ts!", "src/**/*.test.*"],
+-	"entry": ["script/*e2e.js", "src/index.ts", "src/**/*.test.*"],
 -	"ignoreDependencies": ["all-contributors-cli", "cspell-populate-words"],
-+	"entry": ["src/index.ts!"],
++	"entry": ["src/index.ts"],
  	"ignoreExportsUsedInFile": { "interface": true, "type": true },
--	"project": ["src/**/*.ts!", "script/**/*.js"]
-+	"project": ["src/**/*.ts!"]
+-	"project": ["src/**/*.ts", "script/**/*.js"]
++	"project": ["src/**/*.ts"]
  }"
 `;
 
diff --git a/src/next/blocks/blockESLintNode.test.ts b/src/next/blocks/blockESLintNode.test.ts
new file mode 100644
index 0000000..787199f
--- /dev/null
+++ b/src/next/blocks/blockESLintNode.test.ts
@@ -0,0 +1,99 @@
+import { testBlock } from "create-testers";
+import { describe, expect, test, vi } from "vitest";
+
+import { blockESLintNode } from "./blockESLintNode.js";
+import { optionsBase } from "./options.fakes.js";
+
+vi.mock("../utils/resolveBin.js", () => ({
+	resolveBin: (bin: string) => `path/to/${bin}`,
+}));
+
+describe("blockESLintNode", () => {
+	test("when options.usage does not have a repository import", () => {
+		const creation = testBlock(blockESLintNode, {
+			options: { ...optionsBase, usage: `...` },
+		});
+
+		expect(creation).toMatchInlineSnapshot(`
+			{
+			  "addons": [
+			    {
+			      "addons": {
+			        "extensions": [
+			          "n.configs["flat/recommended"]",
+			          {
+			            "extends": [
+			              "tseslint.configs.disableTypeChecked",
+			            ],
+			            "files": [
+			              "**/*.md/*.ts",
+			            ],
+			          },
+			        ],
+			        "imports": [
+			          {
+			            "source": "eslint-plugin-n",
+			            "specifier": "n",
+			          },
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			  ],
+			}
+		`);
+	});
+
+	test("when options.usage does have a repository import", () => {
+		const creation = testBlock(blockESLintNode, {
+			options: {
+				...optionsBase,
+				usage: `\`\`\`ts
+import { greet } from "${optionsBase.repository}";
+
+greet();
+\`\`\`
+				`,
+			},
+		});
+
+		expect(creation).toMatchInlineSnapshot(`
+			{
+			  "addons": [
+			    {
+			      "addons": {
+			        "extensions": [
+			          "n.configs["flat/recommended"]",
+			          {
+			            "extends": [
+			              "tseslint.configs.disableTypeChecked",
+			            ],
+			            "files": [
+			              "**/*.md/*.ts",
+			            ],
+			            "rules": {
+			              "n/no-missing-import": [
+			                "error",
+			                {
+			                  "allowModules": [
+			                    "test-repository",
+			                  ],
+			                },
+			              ],
+			            },
+			          },
+			        ],
+			        "imports": [
+			          {
+			            "source": "eslint-plugin-n",
+			            "specifier": "n",
+			          },
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			  ],
+			}
+		`);
+	});
+});
diff --git a/src/next/blocks/blockMarkdownlint.test.ts b/src/next/blocks/blockMarkdownlint.test.ts
index 749d102..db00740 100644
--- a/src/next/blocks/blockMarkdownlint.test.ts
+++ b/src/next/blocks/blockMarkdownlint.test.ts
@@ -78,7 +78,8 @@ describe("blockMarkdownlint", () => {
 			    ".markdownlint.json": "{"extends":"markdownlint/style/prettier","first-line-h1":false,"no-inline-html":false}",
 			    ".markdownlintignore": ".github/CODE_OF_CONDUCT.md
 			CHANGELOG.md
-			node_modules/",
+			node_modules/
+			",
 			  },
 			  "scripts": [
 			    {
@@ -169,7 +170,8 @@ describe("blockMarkdownlint", () => {
 			    ".markdownlintignore": ".github/CODE_OF_CONDUCT.md
 			CHANGELOG.md
 			lib/
-			node_modules/",
+			node_modules/
+			",
 			  },
 			  "scripts": [
 			    {
diff --git a/src/next/blocks/blockPrettier.test.ts b/src/next/blocks/blockPrettier.test.ts
index ef447c7..fc9787c 100644
--- a/src/next/blocks/blockPrettier.test.ts
+++ b/src/next/blocks/blockPrettier.test.ts
@@ -100,7 +100,8 @@ describe("blockPrettier", () => {
 			    },
 			    ".prettierignore": "/.husky
 			/lib
-			/pnpm-lock.yaml",
+			/pnpm-lock.yaml
+			",
 			    ".prettierrc.json": "{"$schema":"http://json.schemastore.org/prettierrc","useTabs":true}",
 			  },
 			  "scripts": [
@@ -211,7 +212,8 @@ describe("blockPrettier", () => {
 			    },
 			    ".prettierignore": "/.husky
 			/lib
-			/pnpm-lock.yaml",
+			/pnpm-lock.yaml
+			",
 			    ".prettierrc.json": "{"$schema":"http://json.schemastore.org/prettierrc","useTabs":true}",
 			  },
 			  "scripts": [
@@ -340,7 +342,8 @@ describe("blockPrettier", () => {
 			    ".prettierignore": "/.husky
 			/lib
 			/pnpm-lock.yaml
-			generated",
+			generated
+			",
 			    ".prettierrc.json": "{"$schema":"http://json.schemastore.org/prettierrc","overrides":[{"files":".nvmrc","options":{"parser":"yaml"}}],"plugins":["prettier-plugin-curly","prettier-plugin-packagejson","prettier-plugin-sh"],"useTabs":true}",
 			  },
 			  "scripts": [
diff --git a/src/steps/writing/creation/createKnipConfig.test.ts b/src/steps/writing/creation/createKnipConfig.test.ts
index 884e5e6..c94e565 100644
--- a/src/steps/writing/creation/createKnipConfig.test.ts
+++ b/src/steps/writing/creation/createKnipConfig.test.ts
@@ -21,12 +21,12 @@ describe("createKnipConfig", () => {
 
 		expect(JSON.parse(packageJson)).toEqual({
 			$schema: `https://unpkg.com/knip@${version}/schema.json`,
-			entry: ["src/index.ts!"],
+			entry: ["src/index.ts"],
 			ignoreExportsUsedInFile: {
 				interface: true,
 				type: true,
 			},
-			project: ["src/**/*.ts!"],
+			project: ["src/**/*.ts"],
 		});
 	});
 
@@ -37,12 +37,12 @@ describe("createKnipConfig", () => {
 
 		expect(JSON.parse(packageJson)).toEqual({
 			$schema: `https://unpkg.com/knip@latest/schema.json`,
-			entry: ["src/index.ts!"],
+			entry: ["src/index.ts"],
 			ignoreExportsUsedInFile: {
 				interface: true,
 				type: true,
 			},
-			project: ["src/**/*.ts!"],
+			project: ["src/**/*.ts"],
 		});
 	});
 
@@ -55,12 +55,12 @@ describe("createKnipConfig", () => {
 
 		expect(JSON.parse(packageJson)).toEqual({
 			$schema: `https://unpkg.com/knip@latest/schema.json`,
-			entry: ["src/index.ts!"],
+			entry: ["src/index.ts"],
 			ignoreExportsUsedInFile: {
 				interface: true,
 				type: true,
 			},
-			project: ["src/**/*.ts!"],
+			project: ["src/**/*.ts"],
 		});
 	});
 });