eslint-plugin-nextjs 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/index.cjs +1494 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +43 -0
  4. package/dist/index.d.ts +42 -1
  5. package/dist/index.js +1455 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/rules/google-font-display.cjs +119 -0
  8. package/dist/rules/google-font-display.cjs.map +1 -0
  9. package/dist/rules/google-font-display.d.cts +8 -0
  10. package/dist/rules/google-font-display.d.ts +8 -0
  11. package/dist/rules/google-font-display.js +92 -0
  12. package/dist/rules/google-font-display.js.map +1 -0
  13. package/dist/rules/google-font-preconnect.cjs +109 -0
  14. package/dist/rules/google-font-preconnect.cjs.map +1 -0
  15. package/dist/rules/google-font-preconnect.d.cts +5 -0
  16. package/dist/rules/google-font-preconnect.d.ts +5 -0
  17. package/dist/rules/google-font-preconnect.js +82 -0
  18. package/dist/rules/google-font-preconnect.js.map +1 -0
  19. package/dist/rules/inline-script-id.cjs +94 -0
  20. package/dist/rules/inline-script-id.cjs.map +1 -0
  21. package/dist/rules/inline-script-id.d.cts +5 -0
  22. package/dist/rules/inline-script-id.d.ts +5 -0
  23. package/dist/rules/inline-script-id.js +67 -0
  24. package/dist/rules/inline-script-id.js.map +1 -0
  25. package/dist/rules/next-script-for-ga.cjs +129 -0
  26. package/dist/rules/next-script-for-ga.cjs.map +1 -0
  27. package/dist/rules/next-script-for-ga.d.cts +5 -0
  28. package/dist/rules/next-script-for-ga.d.ts +5 -0
  29. package/dist/rules/next-script-for-ga.js +102 -0
  30. package/dist/rules/next-script-for-ga.js.map +1 -0
  31. package/dist/rules/no-assign-module-variable.cjs +64 -0
  32. package/dist/rules/no-assign-module-variable.cjs.map +1 -0
  33. package/dist/rules/no-assign-module-variable.d.cts +5 -0
  34. package/dist/rules/no-assign-module-variable.d.ts +5 -0
  35. package/dist/rules/no-assign-module-variable.js +37 -0
  36. package/dist/rules/no-assign-module-variable.js.map +1 -0
  37. package/dist/rules/no-async-client-component.cjs +99 -0
  38. package/dist/rules/no-async-client-component.cjs.map +1 -0
  39. package/dist/rules/no-async-client-component.d.cts +5 -0
  40. package/dist/rules/no-async-client-component.d.ts +5 -0
  41. package/dist/rules/no-async-client-component.js +72 -0
  42. package/dist/rules/no-async-client-component.js.map +1 -0
  43. package/dist/rules/no-before-interactive-script-outside-document.cjs +94 -0
  44. package/dist/rules/no-before-interactive-script-outside-document.cjs.map +1 -0
  45. package/dist/rules/no-before-interactive-script-outside-document.d.cts +5 -0
  46. package/dist/rules/no-before-interactive-script-outside-document.d.ts +5 -0
  47. package/dist/rules/no-before-interactive-script-outside-document.js +59 -0
  48. package/dist/rules/no-before-interactive-script-outside-document.js.map +1 -0
  49. package/dist/rules/no-css-tags.cjs +70 -0
  50. package/dist/rules/no-css-tags.cjs.map +1 -0
  51. package/dist/rules/no-css-tags.d.cts +5 -0
  52. package/dist/rules/no-css-tags.d.ts +5 -0
  53. package/dist/rules/no-css-tags.js +43 -0
  54. package/dist/rules/no-css-tags.js.map +1 -0
  55. package/dist/rules/no-document-import-in-page.cjs +74 -0
  56. package/dist/rules/no-document-import-in-page.cjs.map +1 -0
  57. package/dist/rules/no-document-import-in-page.d.cts +5 -0
  58. package/dist/rules/no-document-import-in-page.d.ts +5 -0
  59. package/dist/rules/no-document-import-in-page.js +39 -0
  60. package/dist/rules/no-document-import-in-page.js.map +1 -0
  61. package/dist/rules/no-duplicate-head.cjs +87 -0
  62. package/dist/rules/no-duplicate-head.cjs.map +1 -0
  63. package/dist/rules/no-duplicate-head.d.cts +5 -0
  64. package/dist/rules/no-duplicate-head.d.ts +5 -0
  65. package/dist/rules/no-duplicate-head.js +60 -0
  66. package/dist/rules/no-duplicate-head.js.map +1 -0
  67. package/dist/rules/no-head-element.cjs +76 -0
  68. package/dist/rules/no-head-element.cjs.map +1 -0
  69. package/dist/rules/no-head-element.d.cts +5 -0
  70. package/dist/rules/no-head-element.d.ts +5 -0
  71. package/dist/rules/no-head-element.js +41 -0
  72. package/dist/rules/no-head-element.js.map +1 -0
  73. package/dist/rules/no-head-import-in-document.cjs +76 -0
  74. package/dist/rules/no-head-import-in-document.cjs.map +1 -0
  75. package/dist/rules/no-head-import-in-document.d.cts +5 -0
  76. package/dist/rules/no-head-import-in-document.d.ts +5 -0
  77. package/dist/rules/no-head-import-in-document.js +41 -0
  78. package/dist/rules/no-head-import-in-document.js.map +1 -0
  79. package/dist/rules/no-html-link-for-pages.cjs +302 -0
  80. package/dist/rules/no-html-link-for-pages.cjs.map +1 -0
  81. package/dist/rules/no-html-link-for-pages.d.cts +5 -0
  82. package/dist/rules/no-html-link-for-pages.d.ts +5 -0
  83. package/dist/rules/no-html-link-for-pages.js +267 -0
  84. package/dist/rules/no-html-link-for-pages.js.map +1 -0
  85. package/dist/rules/no-img-element.cjs +83 -0
  86. package/dist/rules/no-img-element.cjs.map +1 -0
  87. package/dist/rules/no-img-element.d.cts +5 -0
  88. package/dist/rules/no-img-element.d.ts +5 -0
  89. package/dist/rules/no-img-element.js +48 -0
  90. package/dist/rules/no-img-element.js.map +1 -0
  91. package/dist/rules/no-page-custom-font.cjs +184 -0
  92. package/dist/rules/no-page-custom-font.cjs.map +1 -0
  93. package/dist/rules/no-page-custom-font.d.cts +5 -0
  94. package/dist/rules/no-page-custom-font.d.ts +5 -0
  95. package/dist/rules/no-page-custom-font.js +159 -0
  96. package/dist/rules/no-page-custom-font.js.map +1 -0
  97. package/dist/rules/no-script-component-in-head.cjs +74 -0
  98. package/dist/rules/no-script-component-in-head.cjs.map +1 -0
  99. package/dist/rules/no-script-component-in-head.d.cts +5 -0
  100. package/dist/rules/no-script-component-in-head.d.ts +5 -0
  101. package/dist/rules/no-script-component-in-head.js +47 -0
  102. package/dist/rules/no-script-component-in-head.js.map +1 -0
  103. package/dist/rules/no-styled-jsx-in-document.cjs +78 -0
  104. package/dist/rules/no-styled-jsx-in-document.cjs.map +1 -0
  105. package/dist/rules/no-styled-jsx-in-document.d.cts +5 -0
  106. package/dist/rules/no-styled-jsx-in-document.d.ts +5 -0
  107. package/dist/rules/no-styled-jsx-in-document.js +43 -0
  108. package/dist/rules/no-styled-jsx-in-document.js.map +1 -0
  109. package/dist/rules/no-sync-scripts.cjs +64 -0
  110. package/dist/rules/no-sync-scripts.cjs.map +1 -0
  111. package/dist/rules/no-sync-scripts.d.cts +5 -0
  112. package/dist/rules/no-sync-scripts.d.ts +5 -0
  113. package/dist/rules/no-sync-scripts.js +37 -0
  114. package/dist/rules/no-sync-scripts.js.map +1 -0
  115. package/dist/rules/no-title-in-document-head.cjs +78 -0
  116. package/dist/rules/no-title-in-document-head.cjs.map +1 -0
  117. package/dist/rules/no-title-in-document-head.d.cts +5 -0
  118. package/dist/rules/no-title-in-document-head.d.ts +5 -0
  119. package/dist/rules/no-title-in-document-head.js +51 -0
  120. package/dist/rules/no-title-in-document-head.js.map +1 -0
  121. package/dist/rules/no-typos.cjs +133 -0
  122. package/dist/rules/no-typos.cjs.map +1 -0
  123. package/dist/rules/no-typos.d.cts +5 -0
  124. package/dist/rules/no-typos.d.ts +5 -0
  125. package/dist/rules/no-typos.js +98 -0
  126. package/dist/rules/no-typos.js.map +1 -0
  127. package/dist/rules/no-unwanted-polyfillio.cjs +164 -0
  128. package/dist/rules/no-unwanted-polyfillio.cjs.map +1 -0
  129. package/dist/rules/no-unwanted-polyfillio.d.cts +5 -0
  130. package/dist/rules/no-unwanted-polyfillio.d.ts +5 -0
  131. package/dist/rules/no-unwanted-polyfillio.js +137 -0
  132. package/dist/rules/no-unwanted-polyfillio.js.map +1 -0
  133. package/dist/utils/define-rule.cjs +31 -0
  134. package/dist/utils/define-rule.cjs.map +1 -0
  135. package/dist/utils/define-rule.d.cts +5 -0
  136. package/dist/utils/define-rule.d.ts +5 -0
  137. package/dist/utils/define-rule.js +6 -0
  138. package/dist/utils/define-rule.js.map +1 -0
  139. package/dist/utils/get-root-dirs.cjs +60 -0
  140. package/dist/utils/get-root-dirs.cjs.map +1 -0
  141. package/dist/utils/get-root-dirs.d.cts +8 -0
  142. package/dist/utils/get-root-dirs.d.ts +8 -0
  143. package/dist/utils/get-root-dirs.js +25 -0
  144. package/dist/utils/get-root-dirs.js.map +1 -0
  145. package/dist/utils/node-attributes.cjs +67 -0
  146. package/dist/utils/node-attributes.cjs.map +1 -0
  147. package/dist/utils/node-attributes.d.cts +15 -0
  148. package/dist/utils/node-attributes.d.ts +15 -0
  149. package/dist/utils/node-attributes.js +46 -0
  150. package/dist/utils/node-attributes.js.map +1 -0
  151. package/dist/utils/url.cjs +167 -0
  152. package/dist/utils/url.cjs.map +1 -0
  153. package/dist/utils/url.d.cts +35 -0
  154. package/dist/utils/url.d.ts +35 -0
  155. package/dist/utils/url.js +128 -0
  156. package/dist/utils/url.js.map +1 -0
  157. package/package.json +15 -2
package/dist/index.cjs ADDED
@@ -0,0 +1,1494 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ default: () => index_default,
34
+ rules: () => rules
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/utils/define-rule.ts
39
+ var defineRule = (rule) => rule;
40
+
41
+ // src/utils/node-attributes.ts
42
+ var NodeAttributes = class {
43
+ attributes;
44
+ constructor(ASTnode) {
45
+ this.attributes = {};
46
+ ASTnode.attributes.forEach((attribute) => {
47
+ if (!attribute.type || attribute.type !== "JSXAttribute") {
48
+ return;
49
+ }
50
+ if (attribute.value) {
51
+ const value = typeof attribute.value.value === "string" ? attribute.value.value : typeof attribute.value.expression?.value !== "undefined" ? attribute.value.expression.value : attribute.value.expression?.properties;
52
+ this.attributes[attribute.name.name] = {
53
+ hasValue: true,
54
+ value
55
+ };
56
+ } else {
57
+ this.attributes[attribute.name.name] = {
58
+ hasValue: false
59
+ };
60
+ }
61
+ });
62
+ }
63
+ has(attrName) {
64
+ return Boolean(this.attributes[attrName]);
65
+ }
66
+ hasAny() {
67
+ return Boolean(Object.keys(this.attributes).length);
68
+ }
69
+ hasValue(attrName) {
70
+ return Boolean(this.attributes[attrName]?.hasValue);
71
+ }
72
+ value(attrName) {
73
+ const attr = this.attributes[attrName];
74
+ if (!attr) {
75
+ return true;
76
+ }
77
+ if ("hasValue" in attr && attr.hasValue) {
78
+ return attr.value;
79
+ }
80
+ return void 0;
81
+ }
82
+ };
83
+
84
+ // src/rules/google-font-display.ts
85
+ var url = "https://nextjs.org/docs/messages/google-font-display";
86
+ var googleFontDisplay = defineRule({
87
+ create: (context) => ({
88
+ JSXOpeningElement: (node) => {
89
+ let message2;
90
+ if (node.name.name !== "link") {
91
+ return;
92
+ }
93
+ const attributes = new NodeAttributes(node);
94
+ if (!attributes.has("href") || !attributes.hasValue("href")) {
95
+ return;
96
+ }
97
+ const hrefValue = attributes.value("href");
98
+ const isGoogleFont = typeof hrefValue === "string" && hrefValue.startsWith("https://fonts.googleapis.com/css");
99
+ if (isGoogleFont) {
100
+ const params = new URLSearchParams(hrefValue.split("?", 2)[1]);
101
+ const displayValue = params.get("display");
102
+ if (!params.has("display")) {
103
+ message2 = "A font-display parameter is missing (adding `&display=optional` is recommended).";
104
+ } else if (displayValue === "auto" || displayValue === "block" || displayValue === "fallback") {
105
+ message2 = `${displayValue[0]?.toUpperCase() + displayValue.slice(1)} is not recommended.`;
106
+ }
107
+ }
108
+ if (message2) {
109
+ context.report({
110
+ message: `${message2} See: ${url}`,
111
+ node
112
+ });
113
+ }
114
+ }
115
+ }),
116
+ meta: {
117
+ docs: {
118
+ description: "Enforce font-display behavior with Google Fonts.",
119
+ recommended: true,
120
+ url
121
+ },
122
+ schema: [],
123
+ type: "problem"
124
+ }
125
+ });
126
+
127
+ // src/rules/google-font-preconnect.ts
128
+ var url2 = "https://nextjs.org/docs/messages/google-font-preconnect";
129
+ var googleFontPreconnect = defineRule({
130
+ create: (context) => ({
131
+ JSXOpeningElement: (node) => {
132
+ if (node.name.name !== "link") {
133
+ return;
134
+ }
135
+ const attributes = new NodeAttributes(node);
136
+ if (!attributes.has("href") || !attributes.hasValue("href")) {
137
+ return;
138
+ }
139
+ const hrefValue = attributes.value("href");
140
+ const preconnectMissing = !attributes.has("rel") || !attributes.hasValue("rel") || attributes.value("rel") !== "preconnect";
141
+ if (typeof hrefValue === "string" && hrefValue.startsWith("https://fonts.gstatic.com") && preconnectMissing) {
142
+ context.report({
143
+ message: `\`rel="preconnect"\` is missing from Google Font. See: ${url2}`,
144
+ node
145
+ });
146
+ }
147
+ }
148
+ }),
149
+ meta: {
150
+ docs: {
151
+ description: "Ensure `preconnect` is used with Google Fonts.",
152
+ recommended: true,
153
+ url: url2
154
+ },
155
+ schema: [],
156
+ type: "problem"
157
+ }
158
+ });
159
+
160
+ // src/rules/inline-script-id.ts
161
+ var url3 = "https://nextjs.org/docs/messages/inline-script-id";
162
+ var inlineScriptId = defineRule({
163
+ create: (context) => {
164
+ let nextScriptImportName = null;
165
+ return {
166
+ ImportDeclaration: (node) => {
167
+ if (node.source.value === "next/script") {
168
+ nextScriptImportName = node.specifiers[0].local.name;
169
+ }
170
+ },
171
+ JSXElement: (node) => {
172
+ if (nextScriptImportName === null) {
173
+ return;
174
+ }
175
+ if (node.openingElement?.name && node.openingElement.name.name !== nextScriptImportName) {
176
+ return;
177
+ }
178
+ const attributeNames = /* @__PURE__ */ new Set();
179
+ let hasNonCheckableSpreadAttribute = false;
180
+ node.openingElement.attributes.forEach((attribute) => {
181
+ if (hasNonCheckableSpreadAttribute) {
182
+ return;
183
+ }
184
+ if (attribute.type === "JSXAttribute") {
185
+ attributeNames.add(attribute.name.name);
186
+ } else if (attribute.type === "JSXSpreadAttribute") {
187
+ if (attribute.argument?.properties) {
188
+ attribute.argument.properties.forEach((property) => {
189
+ attributeNames.add(property.key.name);
190
+ });
191
+ } else {
192
+ hasNonCheckableSpreadAttribute = true;
193
+ }
194
+ }
195
+ });
196
+ if (hasNonCheckableSpreadAttribute) {
197
+ return;
198
+ }
199
+ if (node.children.length > 0 || attributeNames.has("dangerouslySetInnerHTML")) {
200
+ if (!attributeNames.has("id")) {
201
+ context.report({
202
+ message: `\`next/script\` components with inline content must specify an \`id\` attribute. See: ${url3}`,
203
+ node
204
+ });
205
+ }
206
+ }
207
+ }
208
+ };
209
+ },
210
+ meta: {
211
+ docs: {
212
+ description: "Enforce `id` attribute on `next/script` components with inline content.",
213
+ recommended: true,
214
+ url: url3
215
+ },
216
+ schema: [],
217
+ type: "problem"
218
+ }
219
+ });
220
+
221
+ // src/rules/next-script-for-ga.ts
222
+ var SUPPORTED_SRCS = [
223
+ "www.google-analytics.com/analytics.js",
224
+ "www.googletagmanager.com/gtag/js"
225
+ ];
226
+ var SUPPORTED_HTML_CONTENT_URLS = [
227
+ "www.google-analytics.com/analytics.js",
228
+ "www.googletagmanager.com/gtm.js"
229
+ ];
230
+ var description = "Prefer `next/script` component when using the inline script for Google Analytics.";
231
+ var url4 = "https://nextjs.org/docs/messages/next-script-for-ga";
232
+ var ERROR_MSG = `${description} See: ${url4}`;
233
+ var containsStr = (str, strList) => {
234
+ return strList.some((s) => str.includes(s));
235
+ };
236
+ var nextScriptForGa = defineRule({
237
+ create: (context) => ({
238
+ JSXOpeningElement: (node) => {
239
+ if (node.name.name !== "script") {
240
+ return;
241
+ }
242
+ if (node.attributes.length === 0) {
243
+ return;
244
+ }
245
+ const attributes = new NodeAttributes(node);
246
+ if (typeof attributes.value("src") === "string" && containsStr(attributes.value("src"), SUPPORTED_SRCS)) {
247
+ return context.report({
248
+ message: ERROR_MSG,
249
+ node
250
+ });
251
+ }
252
+ if (attributes.value("dangerouslySetInnerHTML") && attributes.value("dangerouslySetInnerHTML").length > 0) {
253
+ const htmlContent = attributes.value("dangerouslySetInnerHTML")[0].value.quasis?.[0].value.raw;
254
+ if (htmlContent && containsStr(htmlContent, SUPPORTED_HTML_CONTENT_URLS)) {
255
+ context.report({
256
+ message: ERROR_MSG,
257
+ node
258
+ });
259
+ }
260
+ }
261
+ }
262
+ }),
263
+ meta: {
264
+ docs: {
265
+ description,
266
+ recommended: true,
267
+ url: url4
268
+ },
269
+ schema: [],
270
+ type: "problem"
271
+ }
272
+ });
273
+
274
+ // src/rules/no-assign-module-variable.ts
275
+ var url5 = "https://nextjs.org/docs/messages/no-assign-module-variable";
276
+ var noAssignModuleVariable = defineRule({
277
+ create: (context) => ({
278
+ VariableDeclaration: (node) => {
279
+ const moduleVariableFound = node.declarations.some((declaration) => {
280
+ if ("name" in declaration.id) {
281
+ return declaration.id.name === "module";
282
+ }
283
+ return false;
284
+ });
285
+ if (!moduleVariableFound) {
286
+ return;
287
+ }
288
+ context.report({
289
+ message: `Do not assign to the variable \`module\`. See: ${url5}`,
290
+ node
291
+ });
292
+ }
293
+ }),
294
+ meta: {
295
+ docs: {
296
+ description: "Prevent assignment to the `module` variable.",
297
+ recommended: true,
298
+ url: url5
299
+ },
300
+ schema: [],
301
+ type: "problem"
302
+ }
303
+ });
304
+
305
+ // src/rules/no-async-client-component.ts
306
+ var url6 = "https://nextjs.org/docs/messages/no-async-client-component";
307
+ var description2 = "Prevent client components from being async functions.";
308
+ var message = `${description2} See: ${url6}`;
309
+ var isCapitalized = (str) => /[A-Z]/.test(str[0] ?? "");
310
+ var noAsyncClientComponent = defineRule({
311
+ create: (context) => ({
312
+ Program: (node) => {
313
+ let isClientComponent = false;
314
+ for (const block of node.body) {
315
+ if (block.type === "ExpressionStatement" && block.expression.type === "Literal" && block.expression.value === "use client") {
316
+ isClientComponent = true;
317
+ }
318
+ if (block.type === "ExportDefaultDeclaration" && isClientComponent) {
319
+ if (block.declaration?.type === "FunctionDeclaration" && block.declaration.async && isCapitalized(block.declaration.id.name)) {
320
+ context.report({
321
+ message,
322
+ node: block
323
+ });
324
+ }
325
+ if (block.declaration.type === "Identifier" && isCapitalized(block.declaration.name)) {
326
+ const targetName = block.declaration.name;
327
+ const functionDeclaration = node.body.find((localBlock) => {
328
+ if (localBlock.type === "FunctionDeclaration" && localBlock.id.name === targetName) {
329
+ return true;
330
+ }
331
+ if (localBlock.type === "VariableDeclaration" && localBlock.declarations.find(
332
+ (declaration) => declaration.id?.type === "Identifier" && declaration.id.name === targetName
333
+ )) {
334
+ return true;
335
+ }
336
+ return false;
337
+ });
338
+ if (functionDeclaration?.type === "FunctionDeclaration" && functionDeclaration.async) {
339
+ context.report({
340
+ message,
341
+ node: functionDeclaration
342
+ });
343
+ }
344
+ if (functionDeclaration?.type === "VariableDeclaration") {
345
+ const varDeclarator = functionDeclaration.declarations.find(
346
+ (declaration) => declaration.id?.type === "Identifier" && declaration.id.name === targetName
347
+ );
348
+ if (varDeclarator?.init?.type === "ArrowFunctionExpression" && varDeclarator.init.async) {
349
+ context.report({
350
+ message,
351
+ node: functionDeclaration
352
+ });
353
+ }
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }),
360
+ meta: {
361
+ docs: {
362
+ description: description2,
363
+ recommended: true,
364
+ url: url6
365
+ },
366
+ schema: [],
367
+ type: "problem"
368
+ }
369
+ });
370
+
371
+ // src/rules/no-before-interactive-script-outside-document.ts
372
+ var path = __toESM(require("path"), 1);
373
+ var url7 = "https://nextjs.org/docs/messages/no-before-interactive-script-outside-document";
374
+ var convertToCorrectSeparator = (str) => str.replaceAll(/[/\\]/g, path.sep);
375
+ var noBeforeInteractiveScriptOutsideDocument = defineRule({
376
+ create: (context) => {
377
+ let scriptImportName = null;
378
+ return {
379
+ 'ImportDeclaration[source.value="next/script"] > ImportDefaultSpecifier'(node) {
380
+ scriptImportName = node.local.name;
381
+ },
382
+ JSXOpeningElement: (node) => {
383
+ const pathname = convertToCorrectSeparator(context.filename);
384
+ const isInAppDir = pathname.includes(`${path.sep}app${path.sep}`);
385
+ if (isInAppDir) {
386
+ return;
387
+ }
388
+ if (!scriptImportName) {
389
+ return;
390
+ }
391
+ if (node.name && node.name.name !== scriptImportName) {
392
+ return;
393
+ }
394
+ const strategy = node.attributes.find(
395
+ (child) => child.name && child.name.name === "strategy"
396
+ );
397
+ if (!strategy?.value || strategy.value.value !== "beforeInteractive") {
398
+ return;
399
+ }
400
+ const document = context.filename.split("pages", 2)[1];
401
+ if (document && path.parse(document).name.startsWith("_document")) {
402
+ return;
403
+ }
404
+ context.report({
405
+ message: `\`next/script\`'s \`beforeInteractive\` strategy should not be used outside of \`pages/_document.js\`. See: ${url7}`,
406
+ node
407
+ });
408
+ }
409
+ };
410
+ },
411
+ meta: {
412
+ docs: {
413
+ description: "Prevent usage of `next/script`'s `beforeInteractive` strategy outside of `pages/_document.js`.",
414
+ recommended: true,
415
+ url: url7
416
+ },
417
+ schema: [],
418
+ type: "problem"
419
+ }
420
+ });
421
+
422
+ // src/rules/no-css-tags.ts
423
+ var url8 = "https://nextjs.org/docs/messages/no-css-tags";
424
+ var noCssTags = defineRule({
425
+ create: (context) => ({
426
+ JSXOpeningElement: (node) => {
427
+ if (node.name.name !== "link") {
428
+ return;
429
+ }
430
+ if (node.attributes.length === 0) {
431
+ return;
432
+ }
433
+ const attributes = node.attributes.filter(
434
+ (attr) => attr.type === "JSXAttribute"
435
+ );
436
+ if (attributes.find(
437
+ (attr) => attr.name.name === "rel" && attr.value.value === "stylesheet"
438
+ ) && attributes.find(
439
+ (attr) => attr.name.name === "href" && attr.value.type === "Literal" && !/^https?/.test(attr.value.value)
440
+ )) {
441
+ context.report({
442
+ message: `Do not include stylesheets manually. See: ${url8}`,
443
+ node
444
+ });
445
+ }
446
+ }
447
+ }),
448
+ meta: {
449
+ docs: {
450
+ description: "Prevent manual stylesheet tags.",
451
+ recommended: true,
452
+ url: url8
453
+ },
454
+ schema: [],
455
+ type: "problem"
456
+ }
457
+ });
458
+
459
+ // src/rules/no-document-import-in-page.ts
460
+ var path2 = __toESM(require("path"), 1);
461
+ var url9 = "https://nextjs.org/docs/messages/no-document-import-in-page";
462
+ var noDocumentImportInPage = defineRule({
463
+ create: (context) => ({
464
+ ImportDeclaration: (node) => {
465
+ if (node.source.value !== "next/document") {
466
+ return;
467
+ }
468
+ const paths = context.filename.split("pages");
469
+ const page = paths[paths.length - 1];
470
+ if (!page || page.startsWith(`${path2.sep}_document`) || page.startsWith(`${path2.posix.sep}_document`)) {
471
+ return;
472
+ }
473
+ context.report({
474
+ message: `\`<Document />\` from \`next/document\` should not be imported outside of \`pages/_document.js\`. See: ${url9}`,
475
+ node
476
+ });
477
+ }
478
+ }),
479
+ meta: {
480
+ docs: {
481
+ description: "Prevent importing `next/document` outside of `pages/_document.js`.",
482
+ recommended: true,
483
+ url: url9
484
+ },
485
+ schema: [],
486
+ type: "problem"
487
+ }
488
+ });
489
+
490
+ // src/rules/no-duplicate-head.ts
491
+ var url10 = "https://nextjs.org/docs/messages/no-duplicate-head";
492
+ var noDuplicateHead = defineRule({
493
+ create: (context) => {
494
+ const { sourceCode } = context;
495
+ let documentImportName = null;
496
+ return {
497
+ ImportDeclaration: (node) => {
498
+ if (node.source.value === "next/document") {
499
+ const documentImport = node.specifiers.find(
500
+ ({ type }) => type === "ImportDefaultSpecifier"
501
+ );
502
+ if (documentImport?.local) {
503
+ documentImportName = documentImport.local.name;
504
+ }
505
+ }
506
+ },
507
+ ReturnStatement: (node) => {
508
+ const ancestors = sourceCode.getAncestors(node);
509
+ const documentClass = ancestors.find(
510
+ (ancestorNode) => ancestorNode.type === "ClassDeclaration" && ancestorNode.superClass && "name" in ancestorNode.superClass && ancestorNode.superClass.name === documentImportName
511
+ );
512
+ if (!documentClass) {
513
+ return;
514
+ }
515
+ if (node.argument && "children" in node.argument && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
516
+ node.argument.children) {
517
+ const headComponents = node.argument.children.filter(
518
+ (childrenNode) => childrenNode.openingElement?.name && childrenNode.openingElement.name.name === "Head"
519
+ );
520
+ if (headComponents.length > 1) {
521
+ for (let i = 1; i < headComponents.length; i++) {
522
+ context.report({
523
+ message: `Do not include multiple instances of \`<Head/>\`. See: ${url10}`,
524
+ // @ts-expect-error initial override, TODO: fix
525
+ node: headComponents[i]
526
+ });
527
+ }
528
+ }
529
+ }
530
+ }
531
+ };
532
+ },
533
+ meta: {
534
+ docs: {
535
+ description: "Prevent duplicate usage of `<Head>` in `pages/_document.js`.",
536
+ recommended: true,
537
+ url: url10
538
+ },
539
+ schema: [],
540
+ type: "problem"
541
+ }
542
+ });
543
+
544
+ // src/rules/no-head-element.ts
545
+ var import_node_path = __toESM(require("path"), 1);
546
+ var url11 = "https://nextjs.org/docs/messages/no-head-element";
547
+ var noHeadElement = defineRule({
548
+ create: (context) => ({
549
+ JSXOpeningElement: (node) => {
550
+ const paths = context.filename;
551
+ const isInAppDir = () => (
552
+ // TODO: fix
553
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
554
+ paths.includes(`app${import_node_path.default.sep}`) || paths.includes(`app${import_node_path.default.posix.sep}`)
555
+ );
556
+ if (node.name.name !== "head" || isInAppDir()) {
557
+ return;
558
+ }
559
+ context.report({
560
+ message: `Do not use \`<head>\` element. Use \`<Head />\` from \`next/head\` instead. See: ${url11}`,
561
+ node
562
+ });
563
+ }
564
+ }),
565
+ meta: {
566
+ docs: {
567
+ category: "HTML",
568
+ description: "Prevent usage of `<head>` element.",
569
+ recommended: true,
570
+ url: url11
571
+ },
572
+ schema: [],
573
+ type: "problem"
574
+ }
575
+ });
576
+
577
+ // src/rules/no-head-import-in-document.ts
578
+ var path4 = __toESM(require("path"), 1);
579
+ var url12 = "https://nextjs.org/docs/messages/no-head-import-in-document";
580
+ var noHeadImportInDocument = defineRule({
581
+ create: (context) => ({
582
+ ImportDeclaration: (node) => {
583
+ if (node.source.value !== "next/head") {
584
+ return;
585
+ }
586
+ const document = context.filename.split("pages", 2)[1];
587
+ if (!document) {
588
+ return;
589
+ }
590
+ const { dir, name: name2 } = path4.parse(document);
591
+ if (name2.startsWith("_document") || dir === "/_document" && name2 === "index") {
592
+ context.report({
593
+ message: `\`next/head\` should not be imported in \`pages${document}\`. Use \`<Head />\` from \`next/document\` instead. See: ${url12}`,
594
+ node
595
+ });
596
+ }
597
+ }
598
+ }),
599
+ meta: {
600
+ docs: {
601
+ description: "Prevent usage of `next/head` in `pages/_document.js`.",
602
+ recommended: true,
603
+ url: url12
604
+ },
605
+ schema: [],
606
+ type: "problem"
607
+ }
608
+ });
609
+
610
+ // src/rules/no-html-link-for-pages.ts
611
+ var fs2 = __toESM(require("fs"), 1);
612
+ var path6 = __toESM(require("path"), 1);
613
+
614
+ // src/utils/get-root-dirs.ts
615
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
616
+ var processRootDir = (rootDir) => {
617
+ return import_fast_glob.default.globSync(rootDir.replaceAll("\\", "/"), {
618
+ onlyDirectories: true
619
+ });
620
+ };
621
+ var getRootDirs = (context) => {
622
+ let rootDirs = [context.cwd];
623
+ const nextSettings = (
624
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
625
+ context.settings.next || {}
626
+ );
627
+ const rootDir = nextSettings.rootDir;
628
+ if (typeof rootDir === "string") {
629
+ rootDirs = processRootDir(rootDir);
630
+ } else if (Array.isArray(rootDir)) {
631
+ rootDirs = rootDir.map((dir) => typeof dir === "string" ? processRootDir(dir) : []).flat();
632
+ }
633
+ return rootDirs;
634
+ };
635
+
636
+ // src/utils/url.ts
637
+ var fs = __toESM(require("fs"), 1);
638
+ var path5 = __toESM(require("path"), 1);
639
+ var fsReadDirSyncCache = {};
640
+ var parseUrlForPages = (urlprefix, directory) => {
641
+ fsReadDirSyncCache[directory] ??= fs.readdirSync(directory, {
642
+ withFileTypes: true
643
+ });
644
+ const res = [];
645
+ fsReadDirSyncCache[directory].forEach((dirent) => {
646
+ if (/(?<temp2>\.(?<temp1>j|t)sx?)$/.test(dirent.name)) {
647
+ if (/^index(?<temp2>\.(?<temp1>j|t)sx?)$/.test(dirent.name)) {
648
+ res.push(
649
+ `${urlprefix}${dirent.name.replace(/^index(?<temp2>\.(?<temp1>j|t)sx?)$/, "")}`
650
+ );
651
+ }
652
+ res.push(
653
+ `${urlprefix}${dirent.name.replace(/(?<temp2>\.(?<temp1>j|t)sx?)$/, "")}`
654
+ );
655
+ } else {
656
+ const dirPath = path5.join(directory, dirent.name);
657
+ if (dirent.isDirectory() && !dirent.isSymbolicLink()) {
658
+ res.push(...parseUrlForPages(`${urlprefix + dirent.name}/`, dirPath));
659
+ }
660
+ }
661
+ });
662
+ return res;
663
+ };
664
+ var parseUrlForAppDir = (urlprefix, directory) => {
665
+ fsReadDirSyncCache[directory] ??= fs.readdirSync(directory, {
666
+ withFileTypes: true
667
+ });
668
+ const res = [];
669
+ fsReadDirSyncCache[directory].forEach((dirent) => {
670
+ if (/(?<temp2>\.(?<temp1>j|t)sx?)$/.test(dirent.name)) {
671
+ if (/^page(?<temp2>\.(?<temp1>j|t)sx?)$/.test(dirent.name)) {
672
+ res.push(
673
+ `${urlprefix}${dirent.name.replace(/^page(?<temp2>\.(?<temp1>j|t)sx?)$/, "")}`
674
+ );
675
+ } else if (!/^layout(?<temp2>\.(?<temp1>j|t)sx?)$/.test(dirent.name)) {
676
+ res.push(
677
+ `${urlprefix}${dirent.name.replace(/(?<temp2>\.(?<temp1>j|t)sx?)$/, "")}`
678
+ );
679
+ }
680
+ } else {
681
+ const dirPath = path5.join(directory, dirent.name);
682
+ if (dirent.isDirectory(dirPath) && !dirent.isSymbolicLink()) {
683
+ res.push(...parseUrlForPages(`${urlprefix + dirent.name}/`, dirPath));
684
+ }
685
+ }
686
+ });
687
+ return res;
688
+ };
689
+ var normalizeURL = (url21) => {
690
+ if (!url21) {
691
+ return;
692
+ }
693
+ url21 = url21.split("?", 1)[0];
694
+ url21 = url21.split("#", 1)[0];
695
+ url21 = url21.replace(/(?<temp1>\/index\.html)$/, "/");
696
+ if (url21 === "") {
697
+ return url21;
698
+ }
699
+ url21 = url21.endsWith("/") ? url21 : `${url21}/`;
700
+ return url21;
701
+ };
702
+ var normalizeAppPath = (route) => ensureLeadingSlash(
703
+ route.split("/").reduce((pathname, segment, index, segments) => {
704
+ if (!segment) {
705
+ return pathname;
706
+ }
707
+ if (isGroupSegment(segment)) {
708
+ return pathname;
709
+ }
710
+ if (segment.startsWith("@")) {
711
+ return pathname;
712
+ }
713
+ if ((segment === "page" || segment === "route") && index === segments.length - 1) {
714
+ return pathname;
715
+ }
716
+ return `${pathname}/${segment}`;
717
+ }, "")
718
+ );
719
+ var getUrlFromPagesDirectories = (urlPrefix, directories) => Array.from(
720
+ // De-duplicate similar pages across multiple directories.
721
+ new Set(
722
+ directories.flatMap((directory) => parseUrlForPages(urlPrefix, directory)).map(
723
+ // Since the URLs are normalized we add `^` and `$` to the RegExp to make sure they match exactly.
724
+ (url21) => `^${normalizeURL(url21)}$`
725
+ )
726
+ )
727
+ ).map((urlReg) => {
728
+ urlReg = urlReg.replaceAll(/\[.*]/g, "((?!.+?\\..+?).*?)");
729
+ return new RegExp(urlReg);
730
+ });
731
+ var getUrlFromAppDirectory = (urlPrefix, directories) => Array.from(
732
+ // De-duplicate similar pages across multiple directories.
733
+ new Set(
734
+ directories.map((directory) => parseUrlForAppDir(urlPrefix, directory)).flat().map(
735
+ // Since the URLs are normalized we add `^` and `$` to the RegExp to make sure they match exactly.
736
+ (url21) => `^${normalizeAppPath(url21)}$`
737
+ )
738
+ )
739
+ ).map((urlReg) => {
740
+ urlReg = urlReg.replaceAll(/\[.*]/g, "((?!.+?\\..+?).*?)");
741
+ return new RegExp(urlReg);
742
+ });
743
+ var execOnce = (fn) => {
744
+ let used = false;
745
+ let result;
746
+ return (...args) => {
747
+ if (!used) {
748
+ used = true;
749
+ result = fn(...args);
750
+ }
751
+ return result;
752
+ };
753
+ };
754
+ var ensureLeadingSlash = (route) => route.startsWith("/") ? route : `/${route}`;
755
+ var isGroupSegment = (segment) => segment.startsWith("(") && segment.endsWith(")");
756
+
757
+ // src/rules/no-html-link-for-pages.ts
758
+ var pagesDirWarning = execOnce((pagesDirs) => {
759
+ console.warn(
760
+ `Pages directory cannot be found at ${pagesDirs.join(" or ")}. If using a custom path, please configure with the \`no-html-link-for-pages\` rule in your eslint config file.`
761
+ );
762
+ });
763
+ var fsExistsSyncCache = {};
764
+ var memoize = (fn) => {
765
+ const cache = {};
766
+ return (...args) => {
767
+ const key = JSON.stringify(args);
768
+ cache[key] ??= fn(...args);
769
+ return cache[key];
770
+ };
771
+ };
772
+ var cachedGetUrlFromPagesDirectories = memoize(getUrlFromPagesDirectories);
773
+ var cachedGetUrlFromAppDirectory = memoize(getUrlFromAppDirectory);
774
+ var url13 = "https://nextjs.org/docs/messages/no-html-link-for-pages";
775
+ var noHtmlLinkForPages = defineRule({
776
+ /**
777
+ * Creates an ESLint rule listener.
778
+ */
779
+ create: (context) => {
780
+ const ruleOptions = context.options;
781
+ const [customPagesDirectory] = ruleOptions;
782
+ const rootDirs = getRootDirs(context);
783
+ const pagesDirs = (customPagesDirectory ? [customPagesDirectory] : rootDirs.map((dir) => [
784
+ path6.join(dir, "pages"),
785
+ path6.join(dir, "src", "pages")
786
+ ])).flat();
787
+ const foundPagesDirs = pagesDirs.filter((dir) => {
788
+ fsExistsSyncCache[dir] ??= fs2.existsSync(dir);
789
+ return fsExistsSyncCache[dir];
790
+ });
791
+ const appDirs = rootDirs.map((dir) => [path6.join(dir, "app"), path6.join(dir, "src", "app")]).flat();
792
+ const foundAppDirs = appDirs.filter((dir) => {
793
+ fsExistsSyncCache[dir] ??= fs2.existsSync(dir);
794
+ return fsExistsSyncCache[dir];
795
+ });
796
+ if (foundPagesDirs.length === 0 && foundAppDirs.length === 0) {
797
+ pagesDirWarning(pagesDirs);
798
+ return {};
799
+ }
800
+ const pageUrls = cachedGetUrlFromPagesDirectories("/", foundPagesDirs);
801
+ const appDirUrls = cachedGetUrlFromAppDirectory("/", foundAppDirs);
802
+ const allUrlRegex = [...pageUrls, ...appDirUrls];
803
+ return {
804
+ JSXOpeningElement: (node) => {
805
+ if (node.name.name !== "a") {
806
+ return;
807
+ }
808
+ if (node.attributes.length === 0) {
809
+ return;
810
+ }
811
+ const target = node.attributes.find(
812
+ (attr) => attr.type === "JSXAttribute" && attr.name.name === "target"
813
+ );
814
+ if (target && target.value.value === "_blank") {
815
+ return;
816
+ }
817
+ const href = node.attributes.find(
818
+ (attr) => attr.type === "JSXAttribute" && attr.name.name === "href"
819
+ );
820
+ if (!href || href.value && href.value.type !== "Literal") {
821
+ return;
822
+ }
823
+ const hasDownloadAttr = node.attributes.find(
824
+ (attr) => attr.type === "JSXAttribute" && attr.name.name === "download"
825
+ );
826
+ if (hasDownloadAttr) {
827
+ return;
828
+ }
829
+ const hrefPath = normalizeURL(href.value.value);
830
+ if (/^(?<temp1>https?:\/\/|\/\/)/.test(hrefPath)) {
831
+ return;
832
+ }
833
+ allUrlRegex.forEach((foundUrl) => {
834
+ if (hrefPath && foundUrl.test(normalizeURL(hrefPath))) {
835
+ context.report({
836
+ message: `Do not use an \`<a>\` element to navigate to \`${hrefPath}\`. Use \`<Link />\` from \`next/link\` instead. See: ${url13}`,
837
+ node
838
+ });
839
+ }
840
+ });
841
+ }
842
+ };
843
+ },
844
+ meta: {
845
+ docs: {
846
+ category: "HTML",
847
+ description: "Prevent usage of `<a>` elements to navigate to internal Next.js pages.",
848
+ recommended: true,
849
+ url: url13
850
+ },
851
+ schema: [
852
+ {
853
+ oneOf: [
854
+ {
855
+ type: "string"
856
+ },
857
+ {
858
+ items: {
859
+ type: "string"
860
+ },
861
+ type: "array",
862
+ uniqueItems: true
863
+ }
864
+ ]
865
+ }
866
+ ],
867
+ type: "problem"
868
+ }
869
+ });
870
+
871
+ // src/rules/no-img-element.ts
872
+ var import_node_path2 = __toESM(require("path"), 1);
873
+ var url14 = "https://nextjs.org/docs/messages/no-img-element";
874
+ var noImgElement = defineRule({
875
+ create: (context) => {
876
+ const relativePath = context.filename.replace(import_node_path2.default.sep, "/").replace(context.cwd, "").replace(/^\//, "");
877
+ const isAppDir = /^(?<temp1>src\/)?app\//.test(relativePath);
878
+ return {
879
+ JSXOpeningElement: (node) => {
880
+ if (node.name.name !== "img") {
881
+ return;
882
+ }
883
+ if (node.attributes.length === 0) {
884
+ return;
885
+ }
886
+ if (node.parent?.parent?.openingElement?.name?.name === "picture") {
887
+ return;
888
+ }
889
+ if (isAppDir && /\/opengraph-image|twitter-image|icon\.\w+$/.test(relativePath)) {
890
+ return;
891
+ }
892
+ context.report({
893
+ message: `Using \`<img>\` could result in slower LCP and higher bandwidth. Consider using \`<Image />\` from \`next/image\` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: ${url14}`,
894
+ node
895
+ });
896
+ }
897
+ };
898
+ },
899
+ meta: {
900
+ docs: {
901
+ category: "HTML",
902
+ description: "Prevent usage of `<img>` element due to slower LCP and higher bandwidth.",
903
+ recommended: true,
904
+ url: url14
905
+ },
906
+ schema: [],
907
+ type: "problem"
908
+ }
909
+ });
910
+
911
+ // src/rules/no-page-custom-font.ts
912
+ var import_node_path3 = require("path");
913
+ var url15 = "https://nextjs.org/docs/messages/no-page-custom-font";
914
+ var isIdentifierMatch = (id1, id2) => id1 === null && id2 === null || id1 && id2 && id1.name === id2.name;
915
+ var noPageCustomFont = defineRule({
916
+ create: (context) => {
917
+ const { sourceCode } = context;
918
+ const paths = context.filename.split("pages");
919
+ const page = paths[paths.length - 1];
920
+ if (!page) {
921
+ return {};
922
+ }
923
+ const isDocument = (
924
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
925
+ page.startsWith(`${import_node_path3.sep}_document`) || page.startsWith(`${import_node_path3.posix.sep}_document`)
926
+ );
927
+ let documentImportName;
928
+ let localDefaultExportId;
929
+ let exportDeclarationType;
930
+ return {
931
+ ExportDefaultDeclaration: (node) => {
932
+ exportDeclarationType = node.declaration.type;
933
+ if (node.declaration.type === "FunctionDeclaration") {
934
+ localDefaultExportId = node.declaration.id;
935
+ return;
936
+ }
937
+ if (node.declaration.type === "ClassDeclaration" && node.declaration.superClass && "name" in node.declaration.superClass && node.declaration.superClass.name === documentImportName) {
938
+ localDefaultExportId = node.declaration.id;
939
+ }
940
+ },
941
+ ImportDeclaration: (node) => {
942
+ if (node.source.value === "next/document") {
943
+ const documentImport = node.specifiers.find(
944
+ ({ type }) => type === "ImportDefaultSpecifier"
945
+ );
946
+ if (documentImport?.local) {
947
+ documentImportName = documentImport.local.name;
948
+ }
949
+ }
950
+ },
951
+ JSXOpeningElement: (node) => {
952
+ if (node.name.name !== "link") {
953
+ return;
954
+ }
955
+ const ancestors = sourceCode.getAncestors(node);
956
+ if (!localDefaultExportId) {
957
+ const program = ancestors.find(
958
+ (ancestor) => ancestor.type === "Program"
959
+ );
960
+ for (let i = 0; i <= program.tokens.length - 1; i++) {
961
+ if (localDefaultExportId) {
962
+ break;
963
+ }
964
+ const token = program.tokens[i];
965
+ if (token?.type === "Keyword" && token.value === "export") {
966
+ const nextToken = program.tokens[i + 1];
967
+ if (nextToken && nextToken.type === "Keyword" && nextToken.value === "default") {
968
+ const maybeIdentifier = program.tokens[i + 2];
969
+ if (maybeIdentifier && maybeIdentifier.type === "Identifier") {
970
+ localDefaultExportId = { name: maybeIdentifier.value };
971
+ }
972
+ }
973
+ }
974
+ }
975
+ }
976
+ const parentComponent = ancestors.find((ancestor) => {
977
+ if (exportDeclarationType === "ClassDeclaration") {
978
+ return ancestor.type === exportDeclarationType && "superClass" in ancestor && ancestor.superClass && "name" in ancestor.superClass && ancestor.superClass.name === documentImportName;
979
+ }
980
+ if ("id" in ancestor) {
981
+ if (exportDeclarationType === "FunctionDeclaration") {
982
+ return ancestor.type === exportDeclarationType && isIdentifierMatch(ancestor.id, localDefaultExportId);
983
+ }
984
+ return isIdentifierMatch(ancestor.id, localDefaultExportId);
985
+ }
986
+ return false;
987
+ });
988
+ if (isDocument && parentComponent) {
989
+ return;
990
+ }
991
+ const attributes = new NodeAttributes(node);
992
+ if (!attributes.has("href") || !attributes.hasValue("href")) {
993
+ return;
994
+ }
995
+ const hrefValue = attributes.value("href");
996
+ const isGoogleFont = typeof hrefValue === "string" && hrefValue.startsWith("https://fonts.googleapis.com/css");
997
+ if (isGoogleFont) {
998
+ const end = `This is discouraged. See: ${url15}`;
999
+ const message2 = isDocument ? `Using \`<link />\` outside of \`<Head>\` will disable automatic font optimization. ${end}` : `Custom fonts not added in \`pages/_document.js\` will only load for a single page. ${end}`;
1000
+ context.report({
1001
+ message: message2,
1002
+ node
1003
+ });
1004
+ }
1005
+ }
1006
+ };
1007
+ },
1008
+ meta: {
1009
+ docs: {
1010
+ description: "Prevent page-only custom fonts.",
1011
+ recommended: true,
1012
+ url: url15
1013
+ },
1014
+ schema: [],
1015
+ type: "problem"
1016
+ }
1017
+ });
1018
+
1019
+ // src/rules/no-script-component-in-head.ts
1020
+ var url16 = "https://nextjs.org/docs/messages/no-script-component-in-head";
1021
+ var noScriptComponentInHead = defineRule({
1022
+ create: (context) => {
1023
+ let isNextHead = null;
1024
+ return {
1025
+ ImportDeclaration: (node) => {
1026
+ if (node.source.value === "next/head") {
1027
+ isNextHead = node.source.value;
1028
+ }
1029
+ },
1030
+ JSXElement: (node) => {
1031
+ if (!isNextHead) {
1032
+ return;
1033
+ }
1034
+ if (node.openingElement?.name && node.openingElement.name.name !== "Head") {
1035
+ return;
1036
+ }
1037
+ const scriptTag = node.children.find(
1038
+ (child) => child.openingElement?.name && child.openingElement.name.name === "Script"
1039
+ );
1040
+ if (scriptTag) {
1041
+ context.report({
1042
+ message: `\`next/script\` should not be used in \`next/head\` component. Move \`<Script />\` outside of \`<Head>\` instead. See: ${url16}`,
1043
+ node
1044
+ });
1045
+ }
1046
+ }
1047
+ };
1048
+ },
1049
+ meta: {
1050
+ docs: {
1051
+ description: "Prevent usage of `next/script` in `next/head` component.",
1052
+ recommended: true,
1053
+ url: url16
1054
+ },
1055
+ schema: [],
1056
+ type: "problem"
1057
+ }
1058
+ });
1059
+
1060
+ // src/rules/no-styled-jsx-in-document.ts
1061
+ var path8 = __toESM(require("path"), 1);
1062
+ var url17 = "https://nextjs.org/docs/messages/no-styled-jsx-in-document";
1063
+ var noStyledJsxInDocument = defineRule({
1064
+ create: (context) => ({
1065
+ JSXOpeningElement: (node) => {
1066
+ const document = context.filename.split("pages", 2)[1];
1067
+ if (!document) {
1068
+ return;
1069
+ }
1070
+ const { dir, name: name2 } = path8.parse(document);
1071
+ if (!(name2.startsWith("_document") || dir === "/_document" && name2 === "index")) {
1072
+ return;
1073
+ }
1074
+ if (node.name.name === "style" && node.attributes.find(
1075
+ (attr) => attr.type === "JSXAttribute" && attr.name.name === "jsx"
1076
+ )) {
1077
+ context.report({
1078
+ message: `\`styled-jsx\` should not be used in \`pages/_document.js\`. See: ${url17}`,
1079
+ node
1080
+ });
1081
+ }
1082
+ }
1083
+ }),
1084
+ meta: {
1085
+ docs: {
1086
+ description: "Prevent usage of `styled-jsx` in `pages/_document.js`.",
1087
+ recommended: true,
1088
+ url: url17
1089
+ },
1090
+ schema: [],
1091
+ type: "problem"
1092
+ }
1093
+ });
1094
+
1095
+ // src/rules/no-sync-scripts.ts
1096
+ var url18 = "https://nextjs.org/docs/messages/no-sync-scripts";
1097
+ var noSyncScripts = defineRule({
1098
+ create: (context) => ({
1099
+ JSXOpeningElement: (node) => {
1100
+ if (node.name.name !== "script") {
1101
+ return;
1102
+ }
1103
+ if (node.attributes.length === 0) {
1104
+ return;
1105
+ }
1106
+ const attributeNames = node.attributes.filter((attr) => attr.type === "JSXAttribute").map((attr) => attr.name.name);
1107
+ if (attributeNames.includes("src") && !attributeNames.includes("async") && !attributeNames.includes("defer")) {
1108
+ context.report({
1109
+ message: `Synchronous scripts should not be used. See: ${url18}`,
1110
+ node
1111
+ });
1112
+ }
1113
+ }
1114
+ }),
1115
+ meta: {
1116
+ docs: {
1117
+ description: "Prevent synchronous scripts.",
1118
+ recommended: true,
1119
+ url: url18
1120
+ },
1121
+ schema: [],
1122
+ type: "problem"
1123
+ }
1124
+ });
1125
+
1126
+ // src/rules/no-title-in-document-head.ts
1127
+ var url19 = "https://nextjs.org/docs/messages/no-title-in-document-head";
1128
+ var noTitleInDocumentHead = defineRule({
1129
+ create: (context) => {
1130
+ let headFromNextDocument = false;
1131
+ return {
1132
+ ImportDeclaration: (node) => {
1133
+ if (node.source.value === "next/document") {
1134
+ if (node.specifiers.some(
1135
+ ({ local }) => local.name === "Head"
1136
+ )) {
1137
+ headFromNextDocument = true;
1138
+ }
1139
+ }
1140
+ },
1141
+ JSXElement: (node) => {
1142
+ if (!headFromNextDocument) {
1143
+ return;
1144
+ }
1145
+ if (node.openingElement?.name && node.openingElement.name.name !== "Head") {
1146
+ return;
1147
+ }
1148
+ const titleTag = node.children.find(
1149
+ (child) => child.openingElement?.name && child.openingElement.name.type === "JSXIdentifier" && child.openingElement.name.name === "title"
1150
+ );
1151
+ if (titleTag) {
1152
+ context.report({
1153
+ message: `Do not use \`<title>\` element with \`<Head />\` component from \`next/document\`. Titles should defined at the page-level using \`<Head />\` from \`next/head\` instead. See: ${url19}`,
1154
+ node: titleTag
1155
+ });
1156
+ }
1157
+ }
1158
+ };
1159
+ },
1160
+ meta: {
1161
+ docs: {
1162
+ description: "Prevent usage of `<title>` with `Head` component from `next/document`.",
1163
+ recommended: true,
1164
+ url: url19
1165
+ },
1166
+ schema: [],
1167
+ type: "problem"
1168
+ }
1169
+ });
1170
+
1171
+ // src/rules/no-typos.ts
1172
+ var path9 = __toESM(require("path"), 1);
1173
+ var NEXT_EXPORT_FUNCTIONS = [
1174
+ "getStaticProps",
1175
+ "getStaticPaths",
1176
+ "getServerSideProps"
1177
+ ];
1178
+ var THRESHOLD = 1;
1179
+ var minDistance = (a, b) => {
1180
+ const m = a.length;
1181
+ const n = b.length;
1182
+ if (m < n) {
1183
+ return minDistance(b, a);
1184
+ }
1185
+ if (n === 0) {
1186
+ return m;
1187
+ }
1188
+ let previousRow = Array.from({ length: n + 1 }, (_, i) => i);
1189
+ for (let i = 0; i < m; i++) {
1190
+ const s1 = a[i];
1191
+ const currentRow = [i + 1];
1192
+ for (let j = 0; j < n; j++) {
1193
+ const s2 = b[j];
1194
+ const insertions = previousRow[j + 1] + 1;
1195
+ const deletions = currentRow[j] + 1;
1196
+ const substitutions = previousRow[j] + Number(s1 !== s2);
1197
+ currentRow.push(Math.min(insertions, deletions, substitutions));
1198
+ }
1199
+ previousRow = currentRow;
1200
+ }
1201
+ return previousRow[previousRow.length - 1];
1202
+ };
1203
+ var noTypos = defineRule({
1204
+ create: (context) => {
1205
+ const checkTypos = (node, name2) => {
1206
+ if (NEXT_EXPORT_FUNCTIONS.includes(name2)) {
1207
+ return;
1208
+ }
1209
+ const potentialTypos = NEXT_EXPORT_FUNCTIONS.map((o) => ({
1210
+ distance: minDistance(o, name2) ?? Infinity,
1211
+ option: o
1212
+ })).filter(({ distance }) => distance <= THRESHOLD && distance > 0).sort((a, b) => a.distance - b.distance);
1213
+ if (potentialTypos.length) {
1214
+ context.report({
1215
+ message: `${name2} may be a typo. Did you mean ${potentialTypos[0]?.option}?`,
1216
+ node
1217
+ });
1218
+ }
1219
+ };
1220
+ return {
1221
+ ExportNamedDeclaration: (node) => {
1222
+ const page = context.filename.split("pages", 2)[1];
1223
+ if (!page || path9.parse(page).dir.startsWith("/api")) {
1224
+ return;
1225
+ }
1226
+ const decl = node.declaration;
1227
+ if (!decl) {
1228
+ return;
1229
+ }
1230
+ switch (decl.type) {
1231
+ case "FunctionDeclaration": {
1232
+ checkTypos(node, decl.id.name);
1233
+ break;
1234
+ }
1235
+ case "VariableDeclaration": {
1236
+ decl.declarations.forEach((d) => {
1237
+ if (d.id.type !== "Identifier") {
1238
+ return;
1239
+ }
1240
+ checkTypos(node, d.id.name);
1241
+ });
1242
+ break;
1243
+ }
1244
+ default: {
1245
+ break;
1246
+ }
1247
+ }
1248
+ }
1249
+ };
1250
+ },
1251
+ meta: {
1252
+ docs: {
1253
+ description: "Prevent common typos in Next.js data fetching functions.",
1254
+ recommended: true
1255
+ },
1256
+ schema: [],
1257
+ type: "problem"
1258
+ }
1259
+ });
1260
+
1261
+ // src/rules/no-unwanted-polyfillio.ts
1262
+ var NEXT_POLYFILLED_FEATURES = [
1263
+ "Array.prototype.@@iterator",
1264
+ "Array.prototype.at",
1265
+ "Array.prototype.copyWithin",
1266
+ "Array.prototype.fill",
1267
+ "Array.prototype.find",
1268
+ "Array.prototype.findIndex",
1269
+ "Array.prototype.flatMap",
1270
+ "Array.prototype.flat",
1271
+ "Array.from",
1272
+ "Array.prototype.includes",
1273
+ "Array.of",
1274
+ "Function.prototype.name",
1275
+ "fetch",
1276
+ "Map",
1277
+ "Number.EPSILON",
1278
+ "Number.Epsilon",
1279
+ "Number.isFinite",
1280
+ "Number.isNaN",
1281
+ "Number.isInteger",
1282
+ "Number.isSafeInteger",
1283
+ "Number.MAX_SAFE_INTEGER",
1284
+ "Number.MIN_SAFE_INTEGER",
1285
+ "Number.parseFloat",
1286
+ "Number.parseInt",
1287
+ "Object.assign",
1288
+ "Object.entries",
1289
+ "Object.fromEntries",
1290
+ "Object.getOwnPropertyDescriptor",
1291
+ "Object.getOwnPropertyDescriptors",
1292
+ "Object.hasOwn",
1293
+ "Object.is",
1294
+ "Object.keys",
1295
+ "Object.values",
1296
+ "Reflect",
1297
+ "Set",
1298
+ "Symbol",
1299
+ "Symbol.asyncIterator",
1300
+ "String.prototype.codePointAt",
1301
+ "String.prototype.endsWith",
1302
+ "String.fromCodePoint",
1303
+ "String.prototype.includes",
1304
+ "String.prototype.@@iterator",
1305
+ "String.prototype.padEnd",
1306
+ "String.prototype.padStart",
1307
+ "String.prototype.repeat",
1308
+ "String.raw",
1309
+ "String.prototype.startsWith",
1310
+ "String.prototype.trimEnd",
1311
+ "String.prototype.trimStart",
1312
+ "URL",
1313
+ "URL.prototype.toJSON",
1314
+ "URLSearchParams",
1315
+ "WeakMap",
1316
+ "WeakSet",
1317
+ "Promise",
1318
+ "Promise.prototype.finally",
1319
+ "es2015",
1320
+ // Should be covered by babel-preset-env instead.
1321
+ "es2016",
1322
+ // contains polyfilled 'Array.prototype.includes', 'String.prototype.padEnd' and 'String.prototype.padStart'
1323
+ "es2017",
1324
+ // contains polyfilled 'Object.entries', 'Object.getOwnPropertyDescriptors', 'Object.values', 'String.prototype.padEnd' and 'String.prototype.padStart'
1325
+ "es2018",
1326
+ // contains polyfilled 'Promise.prototype.finally' and ''Symbol.asyncIterator'
1327
+ "es2019",
1328
+ // Contains polyfilled 'Object.fromEntries' and polyfilled 'Array.prototype.flat', 'Array.prototype.flatMap', 'String.prototype.trimEnd' and 'String.prototype.trimStart'
1329
+ "es5",
1330
+ // Should be covered by babel-preset-env instead.
1331
+ "es6",
1332
+ // Should be covered by babel-preset-env instead.
1333
+ "es7"
1334
+ // contains polyfilled 'Array.prototype.includes', 'String.prototype.padEnd' and 'String.prototype.padStart'
1335
+ ];
1336
+ var url20 = "https://nextjs.org/docs/messages/no-unwanted-polyfillio";
1337
+ var noUnwantedPolyfillio = defineRule({
1338
+ create: (context) => {
1339
+ let scriptImport = null;
1340
+ return {
1341
+ ImportDeclaration: (node) => {
1342
+ if (node.source && node.source.value === "next/script") {
1343
+ scriptImport = node.specifiers[0].local.name;
1344
+ }
1345
+ },
1346
+ JSXOpeningElement: (node) => {
1347
+ if (node.name && node.name.name !== "script" && node.name.name !== scriptImport) {
1348
+ return;
1349
+ }
1350
+ if (node.attributes.length === 0) {
1351
+ return;
1352
+ }
1353
+ const srcNode = node.attributes.find(
1354
+ (attr) => attr.type === "JSXAttribute" && attr.name.name === "src"
1355
+ );
1356
+ if (!srcNode || srcNode.value.type !== "Literal") {
1357
+ return;
1358
+ }
1359
+ const src = srcNode.value.value;
1360
+ if (src.startsWith("https://cdn.polyfill.io/v2/") || src.startsWith("https://polyfill.io/v3/") || // https://community.fastly.com/t/new-options-for-polyfill-io-users/2540
1361
+ src.startsWith("https://polyfill-fastly.net/") || src.startsWith("https://polyfill-fastly.io/") || // https://blog.cloudflare.com/polyfill-io-now-available-on-cdnjs-reduce-your-supply-chain-risk
1362
+ src.startsWith("https://cdnjs.cloudflare.com/polyfill/")) {
1363
+ const featureQueryString = new URL(src).searchParams.get("features");
1364
+ const featuresRequested = (featureQueryString ?? "").split(",");
1365
+ const unwantedFeatures = featuresRequested.filter(
1366
+ (feature) => NEXT_POLYFILLED_FEATURES.includes(feature)
1367
+ );
1368
+ if (unwantedFeatures.length > 0) {
1369
+ context.report({
1370
+ message: `No duplicate polyfills from Polyfill.io are allowed. ${unwantedFeatures.join(
1371
+ ", "
1372
+ )} ${unwantedFeatures.length > 1 ? "are" : "is"} already shipped with Next.js. See: ${url20}`,
1373
+ node
1374
+ });
1375
+ }
1376
+ }
1377
+ }
1378
+ };
1379
+ },
1380
+ meta: {
1381
+ docs: {
1382
+ category: "HTML",
1383
+ description: "Prevent duplicate polyfills from Polyfill.io.",
1384
+ recommended: true,
1385
+ url: url20
1386
+ },
1387
+ schema: [],
1388
+ type: "problem"
1389
+ }
1390
+ });
1391
+
1392
+ // src/index.ts
1393
+ var name = "nextjs";
1394
+ var plugin = {
1395
+ name,
1396
+ rules: {
1397
+ [`${name}/google-font-display`]: googleFontDisplay,
1398
+ [`${name}/google-font-preconnect`]: googleFontPreconnect,
1399
+ [`${name}/inline-script-id`]: inlineScriptId,
1400
+ [`${name}/next-script-for-ga`]: nextScriptForGa,
1401
+ [`${name}/no-assign-module-variable`]: noAssignModuleVariable,
1402
+ [`${name}/no-async-client-component`]: noAsyncClientComponent,
1403
+ [`${name}/no-before-interactive-script-outside-document`]: noBeforeInteractiveScriptOutsideDocument,
1404
+ [`${name}/no-css-tags`]: noCssTags,
1405
+ [`${name}/no-document-import-in-page`]: noDocumentImportInPage,
1406
+ [`${name}/no-duplicate-head`]: noDuplicateHead,
1407
+ [`${name}/no-head-element`]: noHeadElement,
1408
+ [`${name}/no-head-import-in-document`]: noHeadImportInDocument,
1409
+ [`${name}/no-html-link-for-pages`]: noHtmlLinkForPages,
1410
+ [`${name}/no-img-element`]: noImgElement,
1411
+ [`${name}/no-page-custom-font`]: noPageCustomFont,
1412
+ [`${name}/no-script-component-in-head`]: noScriptComponentInHead,
1413
+ [`${name}/no-styled-jsx-in-document`]: noStyledJsxInDocument,
1414
+ [`${name}/no-sync-scripts`]: noSyncScripts,
1415
+ [`${name}/no-title-in-document-head`]: noTitleInDocumentHead,
1416
+ [`${name}/no-typos`]: noTypos,
1417
+ [`${name}/no-unwanted-polyfillio`]: noUnwantedPolyfillio
1418
+ }
1419
+ };
1420
+ var recommendedRules = {
1421
+ // warnings
1422
+ "nextjs/google-font-display": "warn",
1423
+ "nextjs/google-font-preconnect": "warn",
1424
+ // errors
1425
+ "nextjs/inline-script-id": "error",
1426
+ "nextjs/next-script-for-ga": "warn",
1427
+ "nextjs/no-assign-module-variable": "error",
1428
+ "nextjs/no-async-client-component": "warn",
1429
+ "nextjs/no-before-interactive-script-outside-document": "warn",
1430
+ "nextjs/no-css-tags": "warn",
1431
+ "nextjs/no-document-import-in-page": "error",
1432
+ "nextjs/no-duplicate-head": "error",
1433
+ "nextjs/no-head-element": "warn",
1434
+ "nextjs/no-head-import-in-document": "error",
1435
+ "nextjs/no-html-link-for-pages": "warn",
1436
+ "nextjs/no-img-element": "warn",
1437
+ "nextjs/no-page-custom-font": "warn",
1438
+ "nextjs/no-script-component-in-head": "error",
1439
+ "nextjs/no-styled-jsx-in-document": "warn",
1440
+ "nextjs/no-sync-scripts": "warn",
1441
+ "nextjs/no-title-in-document-head": "warn",
1442
+ "nextjs/no-typos": "warn",
1443
+ "nextjs/no-unwanted-polyfillio": "warn"
1444
+ };
1445
+ var coreWebVitalsRules = {
1446
+ ...recommendedRules,
1447
+ "nextjs/no-html-link-for-pages": "error",
1448
+ "nextjs/no-sync-scripts": "error"
1449
+ };
1450
+ var createRuleConfig = (pluginName, rules2, isFlat = false) => {
1451
+ return {
1452
+ plugins: isFlat ? { [pluginName]: plugin } : [pluginName],
1453
+ rules: rules2
1454
+ };
1455
+ };
1456
+ var recommendedFlatConfig = createRuleConfig(name, recommendedRules, true);
1457
+ var recommendedLegacyConfig = createRuleConfig(name, recommendedRules, false);
1458
+ var coreWebVitalsFlatConfig = createRuleConfig(
1459
+ name,
1460
+ coreWebVitalsRules,
1461
+ true
1462
+ );
1463
+ var coreWebVitalsLegacyConfig = createRuleConfig(
1464
+ name,
1465
+ coreWebVitalsRules,
1466
+ false
1467
+ );
1468
+ var index_default = {
1469
+ ...plugin,
1470
+ configs: {
1471
+ /**
1472
+ * Legacy config (ESLint < v9) with Core Web Vitals rules (recommended with some warnings upgrade to errors)
1473
+ */
1474
+ "core-web-vitals": coreWebVitalsLegacyConfig,
1475
+ /**
1476
+ * Flat config (ESLint v9+) with Core Web Vitals rules (recommended with some warnings upgrade to errors)
1477
+ */
1478
+ "core-web-vitals/flat": coreWebVitalsFlatConfig,
1479
+ /**
1480
+ * Legacy config (ESLint < v9) with recommended rules
1481
+ */
1482
+ recommended: recommendedLegacyConfig,
1483
+ /**
1484
+ * Flat config (ESLint v9+) with recommended rules
1485
+ */
1486
+ "recommended/flat": recommendedFlatConfig
1487
+ }
1488
+ };
1489
+ var rules = plugin.rules;
1490
+ // Annotate the CommonJS export names for ESM import in node:
1491
+ 0 && (module.exports = {
1492
+ rules
1493
+ });
1494
+ //# sourceMappingURL=index.cjs.map