eslint-plugin-next-on-pages
Version:
`eslint-plugin-next-on-pages` is an ESlint plugin intended to support developers developing Next.js application via `@cloudflare/next-on-pages`.
1,003 lines (978 loc) • 36.7 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// src/rules/no-nodejs-runtime.ts
var require_no_nodejs_runtime = __commonJS({
"src/rules/no-nodejs-runtime.ts"(exports2, module2) {
"use strict";
var rule = {
create: (context) => {
return {
ExportNamedDeclaration: (node) => {
const declaration = node.declaration;
if (declaration?.type === "VariableDeclaration" && declaration.declarations.length === 1 && declaration.declarations[0]?.id.type === "Identifier" && declaration.declarations[0].id.name === "runtime" && declaration.declarations[0].init?.type === "Literal" && declaration.declarations[0].init?.value === "nodejs") {
context.report({
message: "The 'nodejs' runtime is not supported. Use 'edge' instead.",
node: declaration.declarations[0].init,
fix: (fixer) => declaration.declarations[0]?.init ? fixer.replaceText(declaration.declarations[0].init, "'edge'") : null
});
}
}
};
},
meta: {
fixable: "code",
docs: {
url: "https://github.com/cloudflare/next-on-pages/blob/main/packages/eslint-plugin-next-on-pages/docs/rules/no-nodejs-runtime.md"
}
}
};
module2.exports = rule;
}
});
// src/utils/extract-paths.ts
function extractPaths(fullPath) {
const paths = [];
const sections = fullPath.split("/");
if (sections.length === 1)
return paths;
if (sections[0]) {
paths.push(sections[0]);
}
sections.slice(1, -1).forEach((section, i) => {
paths.push(`${paths[i]}/${section}`);
});
return paths;
}
var init_extract_paths = __esm({
"src/utils/extract-paths.ts"() {
"use strict";
}
});
// ../../node_modules/comment-parser/es6/primitives.js
var Markers;
var init_primitives = __esm({
"../../node_modules/comment-parser/es6/primitives.js"() {
(function(Markers2) {
Markers2["start"] = "/**";
Markers2["nostart"] = "/***";
Markers2["delim"] = "*";
Markers2["end"] = "*/";
})(Markers || (Markers = {}));
}
});
// ../../node_modules/comment-parser/es6/util.js
function isSpace(source) {
return /^\s+$/.test(source);
}
function splitCR(source) {
const matches = source.match(/\r+$/);
return matches == null ? ["", source] : [source.slice(-matches[0].length), source.slice(0, -matches[0].length)];
}
function splitSpace(source) {
const matches = source.match(/^\s+/);
return matches == null ? ["", source] : [source.slice(0, matches[0].length), source.slice(matches[0].length)];
}
function splitLines(source) {
return source.split(/\n/);
}
function seedSpec(spec = {}) {
return Object.assign({ tag: "", name: "", type: "", optional: false, description: "", problems: [], source: [] }, spec);
}
function seedTokens(tokens = {}) {
return Object.assign({ start: "", delimiter: "", postDelimiter: "", tag: "", postTag: "", name: "", postName: "", type: "", postType: "", description: "", end: "", lineEnd: "" }, tokens);
}
var init_util = __esm({
"../../node_modules/comment-parser/es6/util.js"() {
}
});
// ../../node_modules/comment-parser/es6/parser/block-parser.js
function getParser({ fence = "```" } = {}) {
const fencer = getFencer(fence);
const toggleFence = (source, isFenced) => fencer(source) ? !isFenced : isFenced;
return function parseBlock(source) {
const sections = [[]];
let isFenced = false;
for (const line of source) {
if (reTag.test(line.tokens.description) && !isFenced) {
sections.push([line]);
} else {
sections[sections.length - 1].push(line);
}
isFenced = toggleFence(line.tokens.description, isFenced);
}
return sections;
};
}
function getFencer(fence) {
if (typeof fence === "string")
return (source) => source.split(fence).length % 2 === 0;
return fence;
}
var reTag;
var init_block_parser = __esm({
"../../node_modules/comment-parser/es6/parser/block-parser.js"() {
reTag = /^@\S+/;
}
});
// ../../node_modules/comment-parser/es6/parser/source-parser.js
function getParser2({ startLine = 0, markers = Markers } = {}) {
let block = null;
let num = startLine;
return function parseSource(source) {
let rest = source;
const tokens = seedTokens();
[tokens.lineEnd, rest] = splitCR(rest);
[tokens.start, rest] = splitSpace(rest);
if (block === null && rest.startsWith(markers.start) && !rest.startsWith(markers.nostart)) {
block = [];
tokens.delimiter = rest.slice(0, markers.start.length);
rest = rest.slice(markers.start.length);
[tokens.postDelimiter, rest] = splitSpace(rest);
}
if (block === null) {
num++;
return null;
}
const isClosed = rest.trimRight().endsWith(markers.end);
if (tokens.delimiter === "" && rest.startsWith(markers.delim) && !rest.startsWith(markers.end)) {
tokens.delimiter = markers.delim;
rest = rest.slice(markers.delim.length);
[tokens.postDelimiter, rest] = splitSpace(rest);
}
if (isClosed) {
const trimmed = rest.trimRight();
tokens.end = rest.slice(trimmed.length - markers.end.length);
rest = trimmed.slice(0, -markers.end.length);
}
tokens.description = rest;
block.push({ number: num, source, tokens });
num++;
if (isClosed) {
const result = block.slice();
block = null;
return result;
}
return null;
};
}
var init_source_parser = __esm({
"../../node_modules/comment-parser/es6/parser/source-parser.js"() {
init_primitives();
init_util();
}
});
// ../../node_modules/comment-parser/es6/parser/spec-parser.js
function getParser3({ tokenizers }) {
return function parseSpec(source) {
var _a;
let spec = seedSpec({ source });
for (const tokenize of tokenizers) {
spec = tokenize(spec);
if ((_a = spec.problems[spec.problems.length - 1]) === null || _a === void 0 ? void 0 : _a.critical)
break;
}
return spec;
};
}
var init_spec_parser = __esm({
"../../node_modules/comment-parser/es6/parser/spec-parser.js"() {
init_util();
}
});
// ../../node_modules/comment-parser/es6/parser/tokenizers/tag.js
function tagTokenizer() {
return (spec) => {
const { tokens } = spec.source[0];
const match = tokens.description.match(/\s*(@(\S+))(\s*)/);
if (match === null) {
spec.problems.push({
code: "spec:tag:prefix",
message: 'tag should start with "@" symbol',
line: spec.source[0].number,
critical: true
});
return spec;
}
tokens.tag = match[1];
tokens.postTag = match[3];
tokens.description = tokens.description.slice(match[0].length);
spec.tag = match[2];
return spec;
};
}
var init_tag = __esm({
"../../node_modules/comment-parser/es6/parser/tokenizers/tag.js"() {
}
});
// ../../node_modules/comment-parser/es6/parser/tokenizers/type.js
function typeTokenizer(spacing = "compact") {
const join2 = getJoiner(spacing);
return (spec) => {
let curlies = 0;
let lines = [];
for (const [i, { tokens }] of spec.source.entries()) {
let type = "";
if (i === 0 && tokens.description[0] !== "{")
return spec;
for (const ch of tokens.description) {
if (ch === "{")
curlies++;
if (ch === "}")
curlies--;
type += ch;
if (curlies === 0)
break;
}
lines.push([tokens, type]);
if (curlies === 0)
break;
}
if (curlies !== 0) {
spec.problems.push({
code: "spec:type:unpaired-curlies",
message: "unpaired curlies",
line: spec.source[0].number,
critical: true
});
return spec;
}
const parts = [];
const offset = lines[0][0].postDelimiter.length;
for (const [i, [tokens, type]] of lines.entries()) {
tokens.type = type;
if (i > 0) {
tokens.type = tokens.postDelimiter.slice(offset) + type;
tokens.postDelimiter = tokens.postDelimiter.slice(0, offset);
}
[tokens.postType, tokens.description] = splitSpace(tokens.description.slice(type.length));
parts.push(tokens.type);
}
parts[0] = parts[0].slice(1);
parts[parts.length - 1] = parts[parts.length - 1].slice(0, -1);
spec.type = join2(parts);
return spec;
};
}
function getJoiner(spacing) {
if (spacing === "compact")
return (t) => t.map(trim).join("");
else if (spacing === "preserve")
return (t) => t.join("\n");
else
return spacing;
}
var trim;
var init_type = __esm({
"../../node_modules/comment-parser/es6/parser/tokenizers/type.js"() {
init_util();
trim = (x) => x.trim();
}
});
// ../../node_modules/comment-parser/es6/parser/tokenizers/name.js
function nameTokenizer() {
const typeEnd = (num, { tokens }, i) => tokens.type === "" ? num : i;
return (spec) => {
const { tokens } = spec.source[spec.source.reduce(typeEnd, 0)];
const source = tokens.description.trimLeft();
const quotedGroups = source.split('"');
if (quotedGroups.length > 1 && quotedGroups[0] === "" && quotedGroups.length % 2 === 1) {
spec.name = quotedGroups[1];
tokens.name = `"${quotedGroups[1]}"`;
[tokens.postName, tokens.description] = splitSpace(source.slice(tokens.name.length));
return spec;
}
let brackets = 0;
let name = "";
let optional = false;
let defaultValue;
for (const ch of source) {
if (brackets === 0 && isSpace(ch))
break;
if (ch === "[")
brackets++;
if (ch === "]")
brackets--;
name += ch;
}
if (brackets !== 0) {
spec.problems.push({
code: "spec:name:unpaired-brackets",
message: "unpaired brackets",
line: spec.source[0].number,
critical: true
});
return spec;
}
const nameToken = name;
if (name[0] === "[" && name[name.length - 1] === "]") {
optional = true;
name = name.slice(1, -1);
const parts = name.split("=");
name = parts[0].trim();
if (parts[1] !== void 0)
defaultValue = parts.slice(1).join("=").trim();
if (name === "") {
spec.problems.push({
code: "spec:name:empty-name",
message: "empty name",
line: spec.source[0].number,
critical: true
});
return spec;
}
if (defaultValue === "") {
spec.problems.push({
code: "spec:name:empty-default",
message: "empty default value",
line: spec.source[0].number,
critical: true
});
return spec;
}
if (!isQuoted(defaultValue) && /=(?!>)/.test(defaultValue)) {
spec.problems.push({
code: "spec:name:invalid-default",
message: "invalid default value syntax",
line: spec.source[0].number,
critical: true
});
return spec;
}
}
spec.optional = optional;
spec.name = name;
tokens.name = nameToken;
if (defaultValue !== void 0)
spec.default = defaultValue;
[tokens.postName, tokens.description] = splitSpace(source.slice(tokens.name.length));
return spec;
};
}
var isQuoted;
var init_name = __esm({
"../../node_modules/comment-parser/es6/parser/tokenizers/name.js"() {
init_util();
isQuoted = (s) => s && s.startsWith('"') && s.endsWith('"');
}
});
// ../../node_modules/comment-parser/es6/parser/tokenizers/description.js
function descriptionTokenizer(spacing = "compact", markers = Markers) {
const join2 = getJoiner2(spacing);
return (spec) => {
spec.description = join2(spec.source, markers);
return spec;
};
}
function getJoiner2(spacing) {
if (spacing === "compact")
return compactJoiner;
if (spacing === "preserve")
return preserveJoiner;
return spacing;
}
function compactJoiner(lines, markers = Markers) {
return lines.map(({ tokens: { description } }) => description.trim()).filter((description) => description !== "").join(" ");
}
function preserveJoiner(lines, markers = Markers) {
if (lines.length === 0)
return "";
if (lines[0].tokens.description === "" && lines[0].tokens.delimiter === markers.start)
lines = lines.slice(1);
const lastLine = lines[lines.length - 1];
if (lastLine !== void 0 && lastLine.tokens.description === "" && lastLine.tokens.end.endsWith(markers.end))
lines = lines.slice(0, -1);
lines = lines.slice(lines.reduce(lineNo, 0));
return lines.map(getDescription).join("\n");
}
var lineNo, getDescription;
var init_description = __esm({
"../../node_modules/comment-parser/es6/parser/tokenizers/description.js"() {
init_primitives();
lineNo = (num, { tokens }, i) => tokens.type === "" ? num : i;
getDescription = ({ tokens }) => (tokens.delimiter === "" ? tokens.start : tokens.postDelimiter.slice(1)) + tokens.description;
}
});
// ../../node_modules/comment-parser/es6/parser/index.js
function getParser4({ startLine = 0, fence = "```", spacing = "compact", markers = Markers, tokenizers = [
tagTokenizer(),
typeTokenizer(spacing),
nameTokenizer(),
descriptionTokenizer(spacing)
] } = {}) {
if (startLine < 0 || startLine % 1 > 0)
throw new Error("Invalid startLine");
const parseSource = getParser2({ startLine, markers });
const parseBlock = getParser({ fence });
const parseSpec = getParser3({ tokenizers });
const joinDescription = getJoiner2(spacing);
return function(source) {
const blocks = [];
for (const line of splitLines(source)) {
const lines = parseSource(line);
if (lines === null)
continue;
const sections = parseBlock(lines);
const specs = sections.slice(1).map(parseSpec);
blocks.push({
description: joinDescription(sections[0], markers),
tags: specs,
source: lines,
problems: specs.reduce((acc, spec) => acc.concat(spec.problems), [])
});
}
return blocks;
};
}
var init_parser = __esm({
"../../node_modules/comment-parser/es6/parser/index.js"() {
init_primitives();
init_util();
init_block_parser();
init_source_parser();
init_spec_parser();
init_tag();
init_type();
init_name();
init_description();
}
});
// ../../node_modules/comment-parser/es6/stringifier/index.js
function join(tokens) {
return tokens.start + tokens.delimiter + tokens.postDelimiter + tokens.tag + tokens.postTag + tokens.type + tokens.postType + tokens.name + tokens.postName + tokens.description + tokens.end + tokens.lineEnd;
}
function getStringifier() {
return (block) => block.source.map(({ tokens }) => join(tokens)).join("\n");
}
var init_stringifier = __esm({
"../../node_modules/comment-parser/es6/stringifier/index.js"() {
}
});
// ../../node_modules/comment-parser/es6/transforms/align.js
var init_align = __esm({
"../../node_modules/comment-parser/es6/transforms/align.js"() {
init_primitives();
init_util();
}
});
// ../../node_modules/comment-parser/es6/transforms/indent.js
var init_indent = __esm({
"../../node_modules/comment-parser/es6/transforms/indent.js"() {
init_util();
}
});
// ../../node_modules/comment-parser/es6/transforms/crlf.js
var init_crlf = __esm({
"../../node_modules/comment-parser/es6/transforms/crlf.js"() {
init_util();
}
});
// ../../node_modules/comment-parser/es6/transforms/index.js
var init_transforms = __esm({
"../../node_modules/comment-parser/es6/transforms/index.js"() {
}
});
// ../../node_modules/comment-parser/es6/stringifier/inspect.js
var zeroWidth, fields;
var init_inspect = __esm({
"../../node_modules/comment-parser/es6/stringifier/inspect.js"() {
init_util();
zeroWidth = {
line: 0,
start: 0,
delimiter: 0,
postDelimiter: 0,
tag: 0,
postTag: 0,
name: 0,
postName: 0,
type: 0,
postType: 0,
description: 0,
end: 0,
lineEnd: 0
};
fields = Object.keys(zeroWidth);
}
});
// ../../node_modules/comment-parser/es6/index.js
function parse(source, options = {}) {
return getParser4(options)(source);
}
var stringify;
var init_es6 = __esm({
"../../node_modules/comment-parser/es6/index.js"() {
init_parser();
init_description();
init_name();
init_tag();
init_type();
init_stringifier();
init_align();
init_indent();
init_crlf();
init_transforms();
init_util();
init_primitives();
init_inspect();
stringify = getStringifier();
}
});
// src/rules/no-unsupported-configs.ts
var require_no_unsupported_configs = __commonJS({
"src/rules/no-unsupported-configs.ts"(exports2, module2) {
"use strict";
init_extract_paths();
init_es6();
var configs = [
{ name: "experimental/appDir", support: "\u2705" },
{ name: "assetPrefix", support: "\u{1F504}" },
{ name: "basePath", support: "\u2705" },
{ name: "compress", support: "N/A" },
{ name: "devIndicators", support: "N/A" },
{ name: "distDir", support: "N/A" },
{ name: "env", support: "\u2705" },
{ name: "eslint", support: "\u2705" },
{ name: "exportPathMap", support: "N/A" },
{ name: "generateBuildId", support: "\u2705" },
{ name: "generateEtags", support: "\u{1F504}" },
{ name: "headers", support: "\u2705" },
{ name: "httpAgentOptions", support: "N/A" },
{ name: "images", support: "\u2705" },
{ name: "incrementalCacheHandlerPath", support: "\u{1F504}" },
{ name: "logging", support: "N/A" },
{ name: "experimental/mdxRs", support: "\u2705" },
{ name: "onDemandEntries", support: "N/A" },
{ name: "experimental/optimizePackageImports", support: "N/A" },
{ name: "output", support: "N/A" },
{ name: "pageExtensions", support: "\u2705" },
{ name: "experimental/ppr", support: "\u274C" },
{ name: "poweredByHeader", support: "\u{1F504}" },
{ name: "productionBrowserSourceMaps", support: "\u{1F504}" },
{ name: "reactStrictMode", support: "\u274C" },
{ name: "redirects", support: "\u2705" },
{ name: "rewrites", support: "\u2705" },
// Runtime Config
{ name: "serverRuntimeConfig", support: "\u274C" },
{ name: "publicRuntimeConfig", support: "\u274C" },
{ name: "experimental/serverActions", support: "\u2705" },
{ name: "serverComponentsExternalPackages", support: "N/A" },
{ name: "trailingSlash", support: "\u2705" },
{ name: "transpilePackages", support: "\u2705" },
{ name: "experimental/turbo", support: "\u{1F504}" },
{ name: "typedRoutes", support: "\u2705" },
{ name: "typescript", support: "\u2705" },
{ name: "experimental/urlImports", support: "\u2705" },
{ name: "webpack", support: "\u2705" },
{ name: "experimental/webVitalsAttribution", support: "\u2705" }
];
function filterAndExtractConfigs(support) {
const comparisonFn = (config2) => Array.isArray(support) ? support.includes(config2.support) : config2.support === support;
return configs.filter(comparisonFn).map((config2) => config2.name);
}
var supportedConfigs = new Set(filterAndExtractConfigs("\u2705"));
var indefinitelyUnsupportedConfigs = new Set(
filterAndExtractConfigs(["\u274C", "N/A"])
);
var currentlyUnsupportedConfigs = new Set(filterAndExtractConfigs("\u{1F504}"));
var nestedConfigPaths = new Set(
[
...supportedConfigs,
...indefinitelyUnsupportedConfigs,
...currentlyUnsupportedConfigs
].flatMap(extractPaths)
);
var ruleSchema = {
type: "object",
properties: {
includeCurrentlyUnsupported: {
type: "boolean",
default: true
},
includeUnrecognized: {
type: "boolean",
default: false
}
}
};
var rule = {
create: (context) => {
const code = context.sourceCode;
const exportedConfigName = context.filename.match(/next\.config\.m?js$/) ? getConfigVariableName(code) : null;
const options = context.options[0] ?? {
includeCurrentlyUnsupported: ruleSchema.properties.includeCurrentlyUnsupported.default,
includeUnrecognized: ruleSchema.properties.includeUnrecognized.default
};
return {
VariableDeclaration: (node) => {
if (!exportedConfigName) {
return;
}
if (node.declarations[0]?.id.type === "Identifier" && node.declarations[0].id.name === exportedConfigName && node.declarations[0].init?.type === "ObjectExpression") {
const nextConfigProps = node.declarations[0].init.properties.filter(
(p) => p.type === "Property"
);
checkConfigPropsRecursively(nextConfigProps, context, options);
}
},
ExpressionStatement: (node) => {
const exportedValue = extractModuleExportValue(node);
if (exportedValue?.type === "ObjectExpression") {
const nextConfigProps = exportedValue.properties.filter(
(p) => p.type === "Property"
);
checkConfigPropsRecursively(nextConfigProps, context, options);
}
}
};
},
meta: {
schema: [ruleSchema],
fixable: "code",
docs: {
url: "https://github.com/cloudflare/next-on-pages/blob/main/packages/eslint-plugin-next-on-pages/docs/rules/no-unsupported-configs.md"
}
}
};
function checkConfigPropsRecursively(nextConfigProps, context, options, propPath = "") {
nextConfigProps.forEach((prop) => {
if (prop.type !== "Property" || prop.key.type !== "Identifier")
return;
const fullPropName = `${propPath}${prop.key.name}`;
if (prop.value.type === "ObjectExpression" && nestedConfigPaths.has(fullPropName)) {
const props = prop.value.properties.filter(
(p) => p.type === "Property"
);
checkConfigPropsRecursively(props, context, options, `${fullPropName}/`);
return;
}
if (indefinitelyUnsupportedConfigs.has(fullPropName)) {
context.report({
message: `The "${fullPropName}" configuration is not supported by next-on-pages (and is unlikely to be supported in the future).`,
node: prop.key
});
return;
}
if (currentlyUnsupportedConfigs.has(fullPropName)) {
if (options.includeCurrentlyUnsupported) {
context.report({
message: `The "${fullPropName}" configuration is not currently supported by next-on-pages.`,
node: prop.key
});
}
return;
}
if (options.includeUnrecognized && !supportedConfigs.has(fullPropName)) {
context.report({
message: `The "${fullPropName}" configuration is not recognized by next-on-pages (it might or might not be supported).`,
node: prop.key
});
return;
}
});
}
function getConfigVariableName(code) {
const { ast } = code;
for (const node of ast.body) {
const exportedValue = extractModuleExportValue(node);
if (exportedValue?.type === "Identifier") {
return exportedValue.name;
}
const esmExportedValue = extractESMExportValue(node);
if (esmExportedValue?.type === "Identifier") {
return esmExportedValue.name;
}
}
const nodeAfterNextConfigComment = getNodeAfterNextConfigTypeComment(code);
if (nodeAfterNextConfigComment?.type === "VariableDeclaration" && nodeAfterNextConfigComment.declarations.length === 1 && nodeAfterNextConfigComment.declarations[0]?.id.type === "Identifier") {
return nodeAfterNextConfigComment.declarations[0]?.id.name;
}
return null;
}
function getNodeAfterNextConfigTypeComment(code) {
return code.ast.body.find((node) => {
const comments = code.getCommentsBefore(node);
return comments.find((comment) => {
const parsedComment = parse(`/*${comment.value}*/`)[0];
return parsedComment?.tags.find(
({ tag, type }) => tag === "type" && /^import\s*\(\s*(['"])next\1\s*\)\s*\.\s*NextConfig$/.test(type)
);
});
}) ?? null;
}
function extractModuleExportValue(node) {
if (node.type === "ExpressionStatement" && node.expression.type === "AssignmentExpression" && node.expression.left.type === "MemberExpression" && node.expression.left.object.type === "Identifier" && node.expression.left.object.name === "module" && node.expression.left.property.type === "Identifier" && node.expression.left.property.name === "exports") {
return node.expression.right;
}
return null;
}
function extractESMExportValue(node) {
if (node.type === "ExportDefaultDeclaration") {
return node.declaration;
}
return null;
}
module2.exports = rule;
}
});
// src/utils/ast-traversal.ts
function getProgramNode(node) {
let currentNode = node;
if (currentNode.type === "Program") {
return currentNode;
}
while (currentNode.parent) {
currentNode = currentNode.parent;
if (currentNode.type === "Program") {
return currentNode;
}
}
return null;
}
function traverseAST(allVisitorKeys, node, visitor) {
const queue = [node];
while (queue.length) {
const currentNode = queue.shift();
visitor(currentNode);
const visitorKeys = allVisitorKeys[currentNode.type];
if (!visitorKeys) {
continue;
}
visitorKeys.forEach((visitorKey) => {
const child = currentNode[visitorKey];
if (!child) {
return;
}
if (Array.isArray(child)) {
for (const item of child) {
queue.push(item);
}
return;
}
queue.push(child);
});
}
}
var init_ast_traversal = __esm({
"src/utils/ast-traversal.ts"() {
"use strict";
}
});
// src/rules/no-app-nodejs-dynamic-ssg.ts
var require_no_app_nodejs_dynamic_ssg = __commonJS({
"src/rules/no-app-nodejs-dynamic-ssg.ts"(exports2, module2) {
"use strict";
init_ast_traversal();
var rule = {
create: (context) => {
const insideAppRouter = context.filename.includes("/app/");
const insideAppApiRoute = context.filename.includes("/app/api");
const isPage = context.filename.match(/page\.[jt]sx/);
if (!insideAppRouter || !isPage || insideAppApiRoute) {
return {};
}
let exportRuntimeEdgeFound = false;
let dynamicParamsFalseFound = false;
return {
ExportNamedDeclaration: (node) => {
const declaration = node.declaration;
if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "generateStaticParams") {
const program = getProgramNode(node);
if (!program) {
return;
}
traverseAST(context.sourceCode.visitorKeys, program, (node2) => {
if (node2.type === "ExportNamedDeclaration") {
if (node2.declaration?.type === "VariableDeclaration" && node2.declaration.declarations.length === 1 && node2.declaration.declarations[0]?.id.type === "Identifier" && node2.declaration.declarations[0].id.name === "runtime" && node2.declaration.declarations[0].init?.type === "Literal" && node2.declaration.declarations[0].init?.value === "edge") {
exportRuntimeEdgeFound = true;
}
if (node2.declaration?.type === "VariableDeclaration" && node2.declaration.declarations.length === 1 && node2.declaration.declarations[0]?.id.type === "Identifier" && node2.declaration.declarations[0].id.name === "dynamicParams" && node2.declaration.declarations[0].init?.type === "Literal" && node2.declaration.declarations[0].init.value === false) {
dynamicParamsFalseFound = true;
}
}
});
if (!exportRuntimeEdgeFound && !dynamicParamsFalseFound) {
context.report({
message: "`generateStaticParams` cannot be used without opting in to the edge runtime or opting out of Dynamic segment handling",
node: declaration.id
});
}
}
}
};
},
meta: {
fixable: "code",
docs: {
url: "https://github.com/cloudflare/next-on-pages/blob/main/packages/eslint-plugin-next-on-pages/docs/rules/no-app-nodejs-dynamic-ssg.md"
}
}
};
module2.exports = rule;
}
});
// src/rules/no-pages-nodejs-dynamic-ssg.ts
var require_no_pages_nodejs_dynamic_ssg = __commonJS({
"src/rules/no-pages-nodejs-dynamic-ssg.ts"(exports2, module2) {
"use strict";
init_ast_traversal();
var rule = {
create: (context) => {
const insidePagesRouter = context.filename.includes("/pages/");
const insidePagesApiRoute = context.filename.includes("/pages/api");
const isJTsx = context.filename.match(/\.[jt]sx$/);
if (!insidePagesRouter || !isJTsx || insidePagesApiRoute) {
return {};
}
let exportRuntimeEdgeFound = false;
let nonFalseFallbackValue = null;
return {
ExportNamedDeclaration: (node) => {
const declaration = node.declaration;
if (declaration?.type === "FunctionDeclaration" && declaration.id?.name === "getStaticPaths") {
traverseAST(
context.sourceCode.visitorKeys,
declaration.body,
(node2) => {
if (node2.type === "ReturnStatement") {
if (node2.argument?.type === "ObjectExpression") {
nonFalseFallbackValue = getNonFalseFallbackPropIfPresent(
node2.argument
);
return;
}
if (node2.argument?.type === "Identifier") {
const identifierName = node2.argument.name;
const body = declaration.body;
traverseAST(
context.sourceCode.visitorKeys,
body,
(node3) => {
if (node3.type === "VariableDeclaration") {
const targetDeclaration = node3.declarations.find(
(declaration2) => declaration2.id.type === "Identifier" && declaration2.id.name === identifierName && declaration2.init?.type === "ObjectExpression"
);
if (targetDeclaration) {
nonFalseFallbackValue = getNonFalseFallbackPropIfPresent(
targetDeclaration.init
);
}
}
}
);
return;
}
}
}
);
const program = getProgramNode(node);
if (!program) {
return;
}
traverseAST(context.sourceCode.visitorKeys, program, (node2) => {
if (node2.type === "ExportNamedDeclaration") {
if (isExperimentalEdgeRuntimeExport(node2) || isConfigExperimentalEdgeRuntimeExport(node2)) {
exportRuntimeEdgeFound = true;
return;
}
isConfigExperimentalEdgeRuntimeExport(node2);
}
});
if (nonFalseFallbackValue && !exportRuntimeEdgeFound) {
context.report({
message: "`getStaticPaths` cannot set `fallback` to anything but `false` without opting in to the edge runtime",
node: nonFalseFallbackValue
});
}
}
}
};
},
meta: {
fixable: "code",
docs: {
url: "https://github.com/cloudflare/next-on-pages/blob/main/packages/eslint-plugin-next-on-pages/docs/rules/no-pages-nodejs-dynamic-ssg.md"
}
}
};
function getNonFalseFallbackPropIfPresent(node) {
const fallbackProp = node.properties.find(
(prop) => prop.type === "Property" && prop.key.type === "Identifier" && prop.key.name === "fallback"
);
if (fallbackProp?.value.type === "Literal" && fallbackProp?.value.value !== false) {
return fallbackProp.value;
}
return null;
}
function isExperimentalEdgeRuntimeExport(node) {
return node.declaration?.type === "VariableDeclaration" && node.declaration.declarations.length === 1 && node.declaration.declarations[0]?.id.type === "Identifier" && node.declaration.declarations[0].id.name === "runtime" && node.declaration.declarations[0].init?.type === "Literal" && node.declaration.declarations[0].init?.value === "experimental-edge";
}
function isConfigExperimentalEdgeRuntimeExport(node) {
if (node.declaration?.type === "VariableDeclaration" && node.declaration.declarations.length === 1 && node.declaration.declarations[0]?.id.type === "Identifier" && node.declaration.declarations[0].id.name === "config" && node.declaration.declarations[0].init?.type === "ObjectExpression") {
const configObj = node.declaration.declarations[0].init;
const runtimeProp = configObj.properties.find(
(prop) => prop.type === "Property" && prop.key.type === "Identifier" && prop.key.name === "runtime"
);
if (runtimeProp?.value.type === "Literal" && runtimeProp?.value.value === "experimental-edge") {
return true;
}
}
return false;
}
module2.exports = rule;
}
});
// src/index.ts
var import_no_nodejs_runtime = __toESM(require_no_nodejs_runtime());
var import_no_unsupported_configs = __toESM(require_no_unsupported_configs());
var import_no_app_nodejs_dynamic_ssg = __toESM(require_no_app_nodejs_dynamic_ssg());
var import_no_pages_nodejs_dynamic_ssg = __toESM(require_no_pages_nodejs_dynamic_ssg());
var config = {
rules: {
"no-nodejs-runtime": import_no_nodejs_runtime.default,
"no-unsupported-configs": import_no_unsupported_configs.default,
"no-app-nodejs-dynamic-ssg": import_no_app_nodejs_dynamic_ssg.default,
"no-pages-nodejs-dynamic-ssg": import_no_pages_nodejs_dynamic_ssg.default,
// the following rule is no longer needed/applicable, it has been converted into a noop (so that it doesn't introduce a breaking change)
// it should be removed in the next package major release
"no-app-not-found-runtime": () => ({})
},
configs: {
recommended: {
plugins: ["eslint-plugin-next-on-pages"],
rules: {
"next-on-pages/no-nodejs-runtime": "error",
"next-on-pages/no-unsupported-configs": "error",
"next-on-pages/no-app-nodejs-dynamic-ssg": "error",
"next-on-pages/no-pages-nodejs-dynamic-ssg": "error"
}
}
}
};
module.exports = config;