From e708538589a41d94b7dd3966286b8650013b837f Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Wed, 4 Sep 2024 21:19:19 -0400 Subject: [PATCH 01/20] move to frameworks.json file so we can share between rust and typescript --- crates/turborepo-lib/src/framework.rs | 145 +++--------------- crates/turborepo-lib/src/frameworks.json | 106 +++++++++++++ crates/turborepo-telemetry/src/events/task.rs | 4 +- 3 files changed, 131 insertions(+), 124 deletions(-) create mode 100644 crates/turborepo-lib/src/frameworks.json diff --git a/crates/turborepo-lib/src/framework.rs b/crates/turborepo-lib/src/framework.rs index 85d6aefd4d0b2..6e3145c771ee4 100644 --- a/crates/turborepo-lib/src/framework.rs +++ b/crates/turborepo-lib/src/framework.rs @@ -1,146 +1,47 @@ use std::sync::OnceLock; +use serde::Deserialize; use turborepo_repository::package_graph::PackageInfo; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] enum Strategy { All, Some, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] struct Matcher { strategy: Strategy, - dependencies: Vec<&'static str>, + dependencies: Vec, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Framework { - slug: &'static str, - env_wildcards: Vec<&'static str>, + slug: String, + env_wildcards: Vec, dependency_match: Matcher, } impl Framework { - pub fn slug(&self) -> &'static str { - self.slug + pub fn slug(&self) -> String { + self.slug.clone() } - pub fn env_wildcards(&self) -> &[&'static str] { + pub fn env_wildcards(&self) -> &[String] { &self.env_wildcards } } -static FRAMEWORKS: OnceLock<[Framework; 13]> = OnceLock::new(); +static FRAMEWORKS: OnceLock> = OnceLock::new(); -fn get_frameworks() -> &'static [Framework] { +const FRAMEWORKS_JSON: &str = include_str!("frameworks.json"); + +fn get_frameworks() -> &'static Vec { FRAMEWORKS.get_or_init(|| { - [ - Framework { - slug: "blitzjs", - env_wildcards: vec!["NEXT_PUBLIC_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["blitz"], - }, - }, - Framework { - slug: "nextjs", - env_wildcards: vec!["NEXT_PUBLIC_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["next"], - }, - }, - Framework { - slug: "gatsby", - env_wildcards: vec!["GATSBY_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["gatsby"], - }, - }, - Framework { - slug: "astro", - env_wildcards: vec!["PUBLIC_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["astro"], - }, - }, - Framework { - slug: "solidstart", - env_wildcards: vec!["VITE_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["solid-js", "solid-start"], - }, - }, - Framework { - slug: "vue", - env_wildcards: vec!["VUE_APP_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["@vue/cli-service"], - }, - }, - Framework { - slug: "sveltekit", - env_wildcards: vec!["VITE_*", "PUBLIC_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["@sveltejs/kit"], - }, - }, - Framework { - slug: "create-react-app", - env_wildcards: vec!["REACT_APP_*"], - dependency_match: Matcher { - strategy: Strategy::Some, - dependencies: vec!["react-scripts", "react-dev-utils"], - }, - }, - Framework { - slug: "nitro", - env_wildcards: vec!["NITRO_*"], - dependency_match: Matcher { - strategy: Strategy::Some, - dependencies: vec!["nitropack", "nitropack-nightly"], - }, - }, - Framework { - slug: "nuxtjs", - env_wildcards: vec!["NUXT_*", "NITRO_*"], - dependency_match: Matcher { - strategy: Strategy::Some, - dependencies: vec!["nuxt", "nuxt-edge", "nuxt3", "nuxt3-edge"], - }, - }, - Framework { - slug: "redwoodjs", - env_wildcards: vec!["REDWOOD_ENV_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["@redwoodjs/core"], - }, - }, - Framework { - slug: "vite", - env_wildcards: vec!["VITE_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["vite"], - }, - }, - Framework { - slug: "sanity", - env_wildcards: vec!["SANITY_STUDIO_*"], - dependency_match: Matcher { - strategy: Strategy::All, - dependencies: vec!["@sanity/cli"], - }, - }, - ] + serde_json::from_str(FRAMEWORKS_JSON).expect("Unable to parse embedded JSON") }) } @@ -159,16 +60,16 @@ impl Matcher { Strategy::All => self .dependencies .iter() - .all(|dep| deps.map_or(false, |deps| deps.contains_key(*dep))), + .all(|dep| deps.map_or(false, |deps| deps.contains_key(dep))), Strategy::Some => self .dependencies .iter() - .any(|dep| deps.map_or(false, |deps| deps.contains_key(*dep))), + .any(|dep| deps.map_or(false, |deps| deps.contains_key(dep))), } } } -pub fn infer_framework(workspace: &PackageInfo, is_monorepo: bool) -> Option<&'static Framework> { +pub fn infer_framework(workspace: &PackageInfo, is_monorepo: bool) -> Option<&Framework> { let frameworks = get_frameworks(); frameworks @@ -183,7 +84,7 @@ mod tests { use crate::framework::{get_frameworks, infer_framework, Framework}; - fn get_framework_by_slug(slug: &str) -> &'static Framework { + fn get_framework_by_slug(slug: &str) -> &Framework { get_frameworks() .iter() .find(|framework| framework.slug == slug) @@ -291,7 +192,7 @@ mod tests { )] fn test_infer_framework( workspace_info: PackageInfo, - expected: Option<&'static Framework>, + expected: Option<&Framework>, is_monorepo: bool, ) { let framework = infer_framework(&workspace_info, is_monorepo); diff --git a/crates/turborepo-lib/src/frameworks.json b/crates/turborepo-lib/src/frameworks.json new file mode 100644 index 0000000000000..eaeca11c51af4 --- /dev/null +++ b/crates/turborepo-lib/src/frameworks.json @@ -0,0 +1,106 @@ +[ + { + "slug": "blitzjs", + "envWildcards": ["NEXT_PUBLIC_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["blitz"] + } + }, + { + "slug": "nextjs", + "envWildcards": ["NEXT_PUBLIC_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["next"] + } + }, + { + "slug": "gatsby", + "envWildcards": ["GATSBY_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["gatsby"] + } + }, + { + "slug": "astro", + "envWildcards": ["PUBLIC_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["astro"] + } + }, + { + "slug": "solidstart", + "envWildcards": ["VITE_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["solid-js", "solid-start"] + } + }, + { + "slug": "vue", + "envWildcards": ["VUE_APP_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["@vue/cli-service"] + } + }, + { + "slug": "sveltekit", + "envWildcards": ["VITE_*", "PUBLIC_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["@sveltejs/kit"] + } + }, + { + "slug": "create-react-app", + "envWildcards": ["REACT_APP_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["react-scripts", "react-dev-utils"] + } + }, + { + "slug": "nitro", + "envWildcards": ["NITRO_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["nitropack", "nitropack-nightly"] + } + }, + { + "slug": "nuxtjs", + "envWildcards": ["NUXT_*", "NITRO_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["nuxt", "nuxt-edge", "nuxt3", "nuxt3-edge"] + } + }, + { + "slug": "redwoodjs", + "envWildcards": ["REDWOOD_ENV_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["@redwoodjs/core"] + } + }, + { + "slug": "vite", + "envWildcards": ["VITE_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["vite"] + } + }, + { + "slug": "sanity", + "envWildcards": ["SANITY_STUDIO_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["@sanity/cli"] + } + } +] diff --git a/crates/turborepo-telemetry/src/events/task.rs b/crates/turborepo-telemetry/src/events/task.rs index 08a97c49852e4..17ab9302cc354 100644 --- a/crates/turborepo-telemetry/src/events/task.rs +++ b/crates/turborepo-telemetry/src/events/task.rs @@ -94,10 +94,10 @@ impl PackageTaskEventBuilder { } // event methods - pub fn track_framework(&self, framework: &str) -> &Self { + pub fn track_framework(&self, framework: String) -> &Self { self.track(Event { key: "framework".to_string(), - value: framework.to_string(), + value: framework, is_sensitive: EventType::NonSensitive, send_in_ci: false, }); From 99e034633105905e2e53f7268e79676f0cd9b991 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Wed, 4 Sep 2024 21:19:49 -0400 Subject: [PATCH 02/20] wip: match frameworks from json file regex --- .../lib/rules/no-undeclared-env-vars.ts | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index 3bd6945a8da98..fd5d4c152def1 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -4,6 +4,16 @@ import type { Node, MemberExpression } from "estree"; import { logger } from "@turbo/utils"; import { RULES } from "../constants"; import { Project, getWorkspaceFromFilePath } from "../utils/calculate-inputs"; +import FRAMEWORKS_JSON from "../../../../crates/turborepo-lib/src/frameworks.json"; // TODO: figure out the best way to export this from the crate and then ingest it here + +interface Framework { + slug: string; + envWildcards: Array; + dependencyMatch: { + strategy: "all" | "some"; + dependencies: Array; + }; +} export interface RuleContextWithOptions extends Rule.RuleContext { options: Array<{ @@ -67,6 +77,44 @@ function normalizeCwd( return undefined; } +const packageJsonDependencies = (filePath: string): Set => { + // get the contents of the package.json + const packageJsonString = path.resolve(filePath); + const packageJson = JSON.parse(packageJsonString) as Record< + string, + Record + >; + return ["dependencies", "devDependencies", "peerDependencies"].reduce( + (acc, key) => { + if (key in packageJson) { + Object.keys(packageJson[key]).forEach((dependency) => { + acc.add(dependency); + }); + } + return acc; + }, + new Set() + ); +}; + +const frameworks = FRAMEWORKS_JSON as Array; + +const matchesFramework = (filePath: string): Array => { + const dependencies = packageJsonDependencies(path.resolve(filePath)); + const matches = frameworks.reduce((acc, framework) => { + const dependenciesMatch = framework.dependencyMatch.dependencies.some( + (dependency) => dependencies.has(dependency) + ); + if (dependenciesMatch) { + framework.envWildcards.forEach((wildcard) => + acc.add(new RegExp(wildcard)) + ); + } + return acc; + }, new Set()); + return Array.from(matches); +}; + function create(context: RuleContextWithOptions): Rule.RuleListener { const { options } = context; @@ -81,6 +129,10 @@ function create(context: RuleContextWithOptions): Rule.RuleListener { } }); + regexAllowList.concat( + matchesFramework("TODO GET PACKAGE.JSON FILENAME SOMEHOW") + ); + const cwd = normalizeCwd( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- needed to support older eslint versions context.getCwd ? context.getCwd() : undefined, @@ -115,18 +167,18 @@ function create(context: RuleContextWithOptions): Rule.RuleListener { if (configured) { return {}; } - let message = `{{ envKey }} is not listed as a dependency in ${ + let message = `1 {{ envKey }} is not listed as a dependency in ${ hasWorkspaceConfigs ? "root turbo.json" : "turbo.json" }`; if (workspaceConfig?.turboConfig) { if (cwd) { // if we have a cwd, we can provide a relative path to the workspace config - message = `{{ envKey }} is not listed as a dependency in the root turbo.json or workspace (${path.relative( + message = `2 {{ envKey }} is not listed as a dependency in the root turbo.json or workspace (${path.relative( cwd, workspaceConfig.workspacePath )}) turbo.json`; } else { - message = `{{ envKey }} is not listed as a dependency in the root turbo.json or workspace turbo.json`; + message = `3 {{ envKey }} is not listed as a dependency in the root turbo.json or workspace turbo.json`; } } From dedf660c41a0a9ee024a0ecccd5fb208bb3bc551 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Wed, 4 Sep 2024 21:19:58 -0400 Subject: [PATCH 03/20] start on test beds --- .../__fixtures__/framework-inference/.eslintrc.js | 4 ++++ .../framework-inference/apps/nextjs/index.js | 1 + .../framework-inference/apps/nextjs/package.json | 10 ++++++++++ .../framework-inference/apps/vite/index.js | 1 + .../framework-inference/apps/vite/package.json | 10 ++++++++++ .../__fixtures__/framework-inference/package.json | 7 +++++++ .../__fixtures__/framework-inference/turbo.json | 7 +++++++ 7 files changed, 40 insertions(+) create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/.eslintrc.js create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/nextjs/index.js create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/nextjs/package.json create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/vite/index.js create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/vite/package.json create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/package.json create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/turbo.json diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/.eslintrc.js b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/.eslintrc.js new file mode 100644 index 0000000000000..8dc66dca7067c --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ["plugin:turbo/recommended"], +}; diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/nextjs/index.js b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/nextjs/index.js new file mode 100644 index 0000000000000..19582df5a92c1 --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/nextjs/index.js @@ -0,0 +1 @@ +process.env.NEXT_PUBLIC_ZILTOID; diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/nextjs/package.json b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/nextjs/package.json new file mode 100644 index 0000000000000..513d44b4c8d2e --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/nextjs/package.json @@ -0,0 +1,10 @@ +{ + "name": "nextjs", + "dependencies": { + "next": "*" + }, + "devDependencies": { + "eslint": "8.57.0", + "eslint-plugin-turbo": "../../../../" + } +} diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/vite/index.js b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/vite/index.js new file mode 100644 index 0000000000000..97dde691a3c90 --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/vite/index.js @@ -0,0 +1 @@ +process.env.VITE_THING; diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/vite/package.json b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/vite/package.json new file mode 100644 index 0000000000000..fba50b3f190e6 --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/vite/package.json @@ -0,0 +1,10 @@ +{ + "name": "vite", + "dependencies": { + "vite": "*" + }, + "devDependencies": { + "eslint": "8.57.0", + "eslint-plugin-turbo": "../../../../" + } +} diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/package.json b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/package.json new file mode 100644 index 0000000000000..ecd2d11d6222a --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/package.json @@ -0,0 +1,7 @@ +{ + "name": "framework-inference", + "devDependencies": { + "eslint": "8.57.0", + "eslint-plugin-turbo": "../../" + } +} diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/turbo.json b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/turbo.json new file mode 100644 index 0000000000000..9116ce5b21177 --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/turbo.json @@ -0,0 +1,7 @@ +{ + "tasks": { + "build": { + "dependsOn": ["^build"] + } + } +} From beb8634d8ba882a8af98016d9387e3ac3e37bfc4 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 5 Sep 2024 08:49:37 -0400 Subject: [PATCH 04/20] it works! --- .../lib/rules/no-undeclared-env-vars.ts | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index fd5d4c152def1..866d7ee51a1e1 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -1,10 +1,18 @@ import path from "node:path"; +import { existsSync, readFileSync } from "node:fs"; import type { Rule } from "eslint"; import type { Node, MemberExpression } from "estree"; import { logger } from "@turbo/utils"; import { RULES } from "../constants"; import { Project, getWorkspaceFromFilePath } from "../utils/calculate-inputs"; -import FRAMEWORKS_JSON from "../../../../crates/turborepo-lib/src/frameworks.json"; // TODO: figure out the best way to export this from the crate and then ingest it here + +/** set this to true if debugging this rule */ +const debugging = "".length === 0; +const debug = debugging + ? logger.info + : (_: string) => { + /* noop */ + }; interface Framework { slug: string; @@ -22,6 +30,28 @@ export interface RuleContextWithOptions extends Rule.RuleContext { }>; } +/** recursively find the closest package.json from the given directory */ +const findClosestPackageJson = (currentDir: string): string | null => { + debug(`searching for package.json in ${currentDir}`); + const packageJsonPath = path.join(currentDir, "package.json"); + + // Check if package.json exists in the current directory + if (existsSync(packageJsonPath)) { + return packageJsonPath; + } + + // Get the parent directory + const parentDir = path.dirname(currentDir); + + // If we've reached the root directory, stop searching + if (parentDir === currentDir) { + return null; + } + + // Recursively search in the parent directory + return findClosestPackageJson(parentDir); +}; + const meta: Rule.RuleMetaData = { type: "problem", docs: { @@ -79,7 +109,7 @@ function normalizeCwd( const packageJsonDependencies = (filePath: string): Set => { // get the contents of the package.json - const packageJsonString = path.resolve(filePath); + const packageJsonString = readFileSync(filePath, "utf-8"); const packageJson = JSON.parse(packageJsonString) as Record< string, Record @@ -97,10 +127,28 @@ const packageJsonDependencies = (filePath: string): Set => { ); }; -const frameworks = FRAMEWORKS_JSON as Array; +const frameworksJsonString = readFileSync( + "../../../../crates/turborepo-lib/src/frameworks.json", + "utf-8" +); +const frameworks = JSON.parse(frameworksJsonString) as Array; +/** + * Turborepo does some nice framework detection based on the dependencies in the package.json. This function ports that logic to the eslint rule. + * + * So, essentially, imagine you have a Vue app. That means you have Vue in your dependencies. This function will return a list of regular expressions that match the environment variables that Vue uses, which is information encoded into the `frameworks.json` file. In Vue's case, it would return a regex that matches `VUE_APP_*` if you have `@vue/cli-service` in your dependencies. + */ const matchesFramework = (filePath: string): Array => { - const dependencies = packageJsonDependencies(path.resolve(filePath)); + const directory = path.dirname(filePath); + const packageJsonPath = findClosestPackageJson(directory); + if (!packageJsonPath) { + logger.error(`No package.json found connected to ${filePath}`); + return []; + } + debug(`found package.json: ${packageJsonPath}`); + + const dependencies = packageJsonDependencies(packageJsonPath); + debug(`dependencies for ${filePath}: ${Array.from(dependencies).join(",")}`); const matches = frameworks.reduce((acc, framework) => { const dependenciesMatch = framework.dependencyMatch.dependencies.some( (dependency) => dependencies.has(dependency) @@ -119,7 +167,7 @@ function create(context: RuleContextWithOptions): Rule.RuleListener { const { options } = context; const allowList: Array = options[0]?.allowList || []; - const regexAllowList: Array = []; + let regexAllowList: Array = []; allowList.forEach((allowed) => { try { regexAllowList.push(new RegExp(allowed)); @@ -129,8 +177,15 @@ function create(context: RuleContextWithOptions): Rule.RuleListener { } }); - regexAllowList.concat( - matchesFramework("TODO GET PACKAGE.JSON FILENAME SOMEHOW") + const filename = context.getFilename(); + debug(`Checking file: ${filename}`); + + const matches = matchesFramework(filename); + regexAllowList = [...regexAllowList, ...matches]; + debug( + `Allow list: ${regexAllowList.map((r) => r.source).join(",")}, ${ + regexAllowList.length + }` ); const cwd = normalizeCwd( From 60bf0992e771eb47ce83f278ce6cf97e0548d210 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 5 Sep 2024 10:12:31 -0400 Subject: [PATCH 05/20] fix typos in frameworks.json --- crates/turborepo-lib/src/frameworks.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/turborepo-lib/src/frameworks.json b/crates/turborepo-lib/src/frameworks.json index eaeca11c51af4..532c75611be23 100644 --- a/crates/turborepo-lib/src/frameworks.json +++ b/crates/turborepo-lib/src/frameworks.json @@ -59,7 +59,7 @@ "slug": "create-react-app", "envWildcards": ["REACT_APP_*"], "dependencyMatch": { - "strategy": "all", + "strategy": "some", "dependencies": ["react-scripts", "react-dev-utils"] } }, @@ -67,7 +67,7 @@ "slug": "nitro", "envWildcards": ["NITRO_*"], "dependencyMatch": { - "strategy": "all", + "strategy": "some", "dependencies": ["nitropack", "nitropack-nightly"] } }, @@ -75,7 +75,7 @@ "slug": "nuxtjs", "envWildcards": ["NUXT_*", "NITRO_*"], "dependencyMatch": { - "strategy": "all", + "strategy": "some", "dependencies": ["nuxt", "nuxt-edge", "nuxt3", "nuxt3-edge"] } }, From 7e1264ef484e3926b20a42ee87b21e398338da96 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 5 Sep 2024 10:13:25 -0400 Subject: [PATCH 06/20] self review --- .../lib/rules/no-undeclared-env-vars.ts | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index 866d7ee51a1e1..ee55f961285b1 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -107,24 +107,23 @@ function normalizeCwd( return undefined; } +/** for a given `package.json` file path, this will compile a Set of that package's listed dependencies */ const packageJsonDependencies = (filePath: string): Set => { // get the contents of the package.json const packageJsonString = readFileSync(filePath, "utf-8"); const packageJson = JSON.parse(packageJsonString) as Record< string, - Record + undefined | Record >; - return ["dependencies", "devDependencies", "peerDependencies"].reduce( - (acc, key) => { - if (key in packageJson) { - Object.keys(packageJson[key]).forEach((dependency) => { - acc.add(dependency); - }); - } - return acc; - }, - new Set() - ); + + return [ + "dependencies", + "devDependencies", + "peerDependencies", + // intentionally not including `optionalDependencies` or `bundleDependencies` because at the time of writing they are not used for any of the frameworks we support + ] + .flatMap((key) => Object.keys(packageJson[key] ?? {})) + .reduce((acc, dependency) => acc.add(dependency), new Set()); }; const frameworksJsonString = readFileSync( @@ -134,33 +133,43 @@ const frameworksJsonString = readFileSync( const frameworks = JSON.parse(frameworksJsonString) as Array; /** - * Turborepo does some nice framework detection based on the dependencies in the package.json. This function ports that logic to the eslint rule. + * Turborepo does some nice framework detection based on the dependencies in the package.json. This function ports that logic to this ESLint rule. * - * So, essentially, imagine you have a Vue app. That means you have Vue in your dependencies. This function will return a list of regular expressions that match the environment variables that Vue uses, which is information encoded into the `frameworks.json` file. In Vue's case, it would return a regex that matches `VUE_APP_*` if you have `@vue/cli-service` in your dependencies. + * Imagine you have a Vue app. That means you have Vue in your `package.json` dependencies. This function will return a list of regular expressions that match the environment variables that Vue depends on, which is information encoded into the `frameworks.json` file. In Vue's case, it would return the regex `VUE_APP_*` since you have `@vue/cli-service` in your dependencies. */ -const matchesFramework = (filePath: string): Array => { +const frameworkEnvMatches = (filePath: string): Set => { const directory = path.dirname(filePath); const packageJsonPath = findClosestPackageJson(directory); if (!packageJsonPath) { logger.error(`No package.json found connected to ${filePath}`); - return []; + return new Set(); } debug(`found package.json: ${packageJsonPath}`); const dependencies = packageJsonDependencies(packageJsonPath); + const hasDependency = (dep: string) => dependencies.has(dep); debug(`dependencies for ${filePath}: ${Array.from(dependencies).join(",")}`); - const matches = frameworks.reduce((acc, framework) => { - const dependenciesMatch = framework.dependencyMatch.dependencies.some( - (dependency) => dependencies.has(dependency) - ); - if (dependenciesMatch) { - framework.envWildcards.forEach((wildcard) => - acc.add(new RegExp(wildcard)) - ); - } - return acc; - }, new Set()); - return Array.from(matches); + + return frameworks.reduce( + ( + acc, + { + dependencyMatch: { dependencies: searchDependencies, strategy }, + envWildcards, + } + ) => { + const hasMatch = + strategy === "all" + ? searchDependencies.every(hasDependency) + : searchDependencies.some(hasDependency); + + if (hasMatch) { + return new Set([...acc, ...envWildcards.map(RegExp)]); + } + return acc; + }, + new Set() + ); }; function create(context: RuleContextWithOptions): Rule.RuleListener { @@ -180,7 +189,7 @@ function create(context: RuleContextWithOptions): Rule.RuleListener { const filename = context.getFilename(); debug(`Checking file: ${filename}`); - const matches = matchesFramework(filename); + const matches = frameworkEnvMatches(filename); regexAllowList = [...regexAllowList, ...matches]; debug( `Allow list: ${regexAllowList.map((r) => r.source).join(",")}, ${ @@ -222,18 +231,18 @@ function create(context: RuleContextWithOptions): Rule.RuleListener { if (configured) { return {}; } - let message = `1 {{ envKey }} is not listed as a dependency in ${ + let message = `{{ envKey }} is not listed as a dependency in ${ hasWorkspaceConfigs ? "root turbo.json" : "turbo.json" }`; if (workspaceConfig?.turboConfig) { if (cwd) { // if we have a cwd, we can provide a relative path to the workspace config - message = `2 {{ envKey }} is not listed as a dependency in the root turbo.json or workspace (${path.relative( + message = `{{ envKey }} is not listed as a dependency in the root turbo.json or workspace (${path.relative( cwd, workspaceConfig.workspacePath )}) turbo.json`; } else { - message = `3 {{ envKey }} is not listed as a dependency in the root turbo.json or workspace turbo.json`; + message = `{{ envKey }} is not listed as a dependency in the root turbo.json or workspace turbo.json`; } } From a9e538250b85b1cffc16797244f50d06814fd580 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 5 Sep 2024 10:22:33 -0400 Subject: [PATCH 07/20] updates documentation --- crates/turborepo-lib/src/frameworks.json | 13 +++++++ .../using-environment-variables.mdx | 34 +++++++++++-------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/crates/turborepo-lib/src/frameworks.json b/crates/turborepo-lib/src/frameworks.json index 532c75611be23..3c02611fe999a 100644 --- a/crates/turborepo-lib/src/frameworks.json +++ b/crates/turborepo-lib/src/frameworks.json @@ -1,6 +1,7 @@ [ { "slug": "blitzjs", + "name": "Blitz.js", "envWildcards": ["NEXT_PUBLIC_*"], "dependencyMatch": { "strategy": "all", @@ -9,6 +10,7 @@ }, { "slug": "nextjs", + "name": "Next.js", "envWildcards": ["NEXT_PUBLIC_*"], "dependencyMatch": { "strategy": "all", @@ -17,6 +19,7 @@ }, { "slug": "gatsby", + "name": "Gatsby", "envWildcards": ["GATSBY_*"], "dependencyMatch": { "strategy": "all", @@ -25,6 +28,7 @@ }, { "slug": "astro", + "name": "Astro", "envWildcards": ["PUBLIC_*"], "dependencyMatch": { "strategy": "all", @@ -33,6 +37,7 @@ }, { "slug": "solidstart", + "name": "SolidStart", "envWildcards": ["VITE_*"], "dependencyMatch": { "strategy": "all", @@ -41,6 +46,7 @@ }, { "slug": "vue", + "name": "Vue", "envWildcards": ["VUE_APP_*"], "dependencyMatch": { "strategy": "all", @@ -49,6 +55,7 @@ }, { "slug": "sveltekit", + "name": "SvelteKit", "envWildcards": ["VITE_*", "PUBLIC_*"], "dependencyMatch": { "strategy": "all", @@ -57,6 +64,7 @@ }, { "slug": "create-react-app", + "name": "Create React App", "envWildcards": ["REACT_APP_*"], "dependencyMatch": { "strategy": "some", @@ -65,6 +73,7 @@ }, { "slug": "nitro", + "name": "Nitro", "envWildcards": ["NITRO_*"], "dependencyMatch": { "strategy": "some", @@ -73,6 +82,7 @@ }, { "slug": "nuxtjs", + "name": "Nuxt.js", "envWildcards": ["NUXT_*", "NITRO_*"], "dependencyMatch": { "strategy": "some", @@ -81,6 +91,7 @@ }, { "slug": "redwoodjs", + "name": "Redwood.js", "envWildcards": ["REDWOOD_ENV_*"], "dependencyMatch": { "strategy": "all", @@ -89,6 +100,7 @@ }, { "slug": "vite", + "name": "Vite", "envWildcards": ["VITE_*"], "dependencyMatch": { "strategy": "all", @@ -97,6 +109,7 @@ }, { "slug": "sanity", + "name": "Sanity", "envWildcards": ["SANITY_STUDIO_*"], "dependencyMatch": { "strategy": "all", diff --git a/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx b/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx index dd244a4baa668..4cc7bb6cd80a9 100644 --- a/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx +++ b/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx @@ -6,6 +6,7 @@ description: Learn how to handle environments for your applications. import { Callout } from '#/components/callout'; import { Tabs, Tab } from '#/components/tabs'; import { Accordion, Accordions } from '#/components/accordion'; +import frameworks from '../../../crates/turborepo-lib/src/frameworks.json'; Environment variable inputs are a vital part of your applications that you'll need to account for in your Turborepo configuration. @@ -56,21 +57,24 @@ Turborepo needs to be aware of your environment variables to account for changes Turborepo automatically adds prefix wildcards to your [`env`](/repo/docs/reference/configuration#env) key for common frameworks. If you're using one of the frameworks below in a package, you don't need to specify environment variables with these prefixes: -| Framework | `env` wildcard | -| ---------------- | ------------------- | -| Astro | `PUBLIC_*` | -| Blitz | `NEXT_PUBLIC_*` | -| Create React App | `REACT_APP_*` | -| Gatsby | `GATSBY_*` | -| Next.js | `NEXT_PUBLIC_*` | -| Nitro | `NITRO_*` | -| Nuxt.js | `NUXT_*`, `NITRO_*` | -| RedwoodJS | `REDWOOD_ENV_*` | -| Sanity Studio | `SANITY_STUDIO_*` | -| Solid | `VITE_*` | -| SvelteKit | `VITE_*` | -| Vite | `VITE_*` | -| Vue | `VUE_APP_*` | + + + + + + + + + {frameworks.map(({ name, envWildcards }) => ( + + + + + ))} + +
Framework + env wildcards +
{name}{envWildcards.join(', ')}
Framework inference is per-package. From e15625876972b40600a2495e16f5f366aee1a201 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 5 Sep 2024 10:23:58 -0400 Subject: [PATCH 08/20] add code wrap for env docs --- .../crafting-your-repository/using-environment-variables.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx b/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx index 4cc7bb6cd80a9..8b17d079ebc39 100644 --- a/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx +++ b/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx @@ -70,7 +70,7 @@ Turborepo automatically adds prefix wildcards to your [`env`](/repo/docs/referen {frameworks.map(({ name, envWildcards }) => ( {name} - {envWildcards.join(', ')} + {envWildcards.map((w) => {w}).join(', ')} ))} From 86e15cb0a4538f53cc87d80392260d381f588f68 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 5 Sep 2024 12:26:46 -0400 Subject: [PATCH 09/20] share json between TypeScript and Rust --- .vscode/tasks.json | 2 +- CONTRIBUTING.md | 2 +- cli/package.json | 6 +++--- crates/turborepo-lib/src/framework.rs | 3 ++- .../using-environment-variables.mdx | 4 ++-- packages/eslint-plugin-turbo/lib/index.ts | 2 -- .../lib/rules/no-undeclared-env-vars.ts | 16 +--------------- packages/eslint-plugin-turbo/package.json | 1 + packages/eslint-plugin-turbo/tsup.config.ts | 3 ++- packages/turbo-benchmark/README.md | 2 +- packages/turbo-types/src/index.ts | 7 +++++++ .../turbo-types/src/json}/frameworks.json | 0 packages/turbo-types/src/types/frameworks.ts | 11 +++++++++++ 13 files changed, 32 insertions(+), 27 deletions(-) rename {crates/turborepo-lib/src => packages/turbo-types/src/json}/frameworks.json (100%) create mode 100644 packages/turbo-types/src/types/frameworks.ts diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 08bf3bcb890b1..77c0a921b1352 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,7 +4,7 @@ { "type": "shell", "label": "prepare turbo", - "command": "cargo build -p turbo" + "command": "cargo build --package turbo" } ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf5d662a49aa3..2cebca83df1cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ Dependencies Building -- Building `turbo` CLI: `cargo build -p turbo` +- Building `turbo` CLI: `cargo build --package turbo` - Using `turbo` to build `turbo` CLI: `./turbow.js` ### TLS Implementation diff --git a/cli/package.json b/cli/package.json index 430365fa92d32..deff9108f9185 100644 --- a/cli/package.json +++ b/cli/package.json @@ -3,8 +3,8 @@ "private": true, "version": "0.0.0", "scripts": { - "clean": "cargo clean -p turbo", - "build": "cargo build -p turbo", - "build:release": "cargo build -p turbo --profile release-turborepo" + "clean": "cargo clean --package turbo", + "build": "cargo build --package turbo", + "build:release": "cargo build --package turbo --profile release-turborepo" } } diff --git a/crates/turborepo-lib/src/framework.rs b/crates/turborepo-lib/src/framework.rs index 6e3145c771ee4..385d651e54d40 100644 --- a/crates/turborepo-lib/src/framework.rs +++ b/crates/turborepo-lib/src/framework.rs @@ -37,7 +37,8 @@ impl Framework { static FRAMEWORKS: OnceLock> = OnceLock::new(); -const FRAMEWORKS_JSON: &str = include_str!("frameworks.json"); +const FRAMEWORKS_JSON: &str = + include_str!("../../../packages/turbo-types/src/json/frameworks.json"); fn get_frameworks() -> &'static Vec { FRAMEWORKS.get_or_init(|| { diff --git a/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx b/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx index 8b17d079ebc39..c994e55c3d888 100644 --- a/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx +++ b/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx @@ -6,7 +6,7 @@ description: Learn how to handle environments for your applications. import { Callout } from '#/components/callout'; import { Tabs, Tab } from '#/components/tabs'; import { Accordion, Accordions } from '#/components/accordion'; -import frameworks from '../../../crates/turborepo-lib/src/frameworks.json'; +import { metadata } from 'eslint-plugin-turbo'; Environment variable inputs are a vital part of your applications that you'll need to account for in your Turborepo configuration. @@ -67,7 +67,7 @@ Turborepo automatically adds prefix wildcards to your [`env`](/repo/docs/referen - {frameworks.map(({ name, envWildcards }) => ( + {metadata.frameworks.map(({ name, envWildcards }) => ( {name} {envWildcards.map((w) => {w}).join(', ')} diff --git a/packages/eslint-plugin-turbo/lib/index.ts b/packages/eslint-plugin-turbo/lib/index.ts index 02cf4ae77ec12..44bd9013f327b 100644 --- a/packages/eslint-plugin-turbo/lib/index.ts +++ b/packages/eslint-plugin-turbo/lib/index.ts @@ -1,7 +1,5 @@ import { RULES } from "./constants"; -// rules import noUndeclaredEnvVars from "./rules/no-undeclared-env-vars"; -// configs import recommended from "./configs/recommended"; const rules = { diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index ee55f961285b1..6f05384dacf78 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -3,6 +3,7 @@ import { existsSync, readFileSync } from "node:fs"; import type { Rule } from "eslint"; import type { Node, MemberExpression } from "estree"; import { logger } from "@turbo/utils"; +import { frameworks } from "@turbo/types"; import { RULES } from "../constants"; import { Project, getWorkspaceFromFilePath } from "../utils/calculate-inputs"; @@ -14,15 +15,6 @@ const debug = debugging /* noop */ }; -interface Framework { - slug: string; - envWildcards: Array; - dependencyMatch: { - strategy: "all" | "some"; - dependencies: Array; - }; -} - export interface RuleContextWithOptions extends Rule.RuleContext { options: Array<{ cwd?: string; @@ -126,12 +118,6 @@ const packageJsonDependencies = (filePath: string): Set => { .reduce((acc, dependency) => acc.add(dependency), new Set()); }; -const frameworksJsonString = readFileSync( - "../../../../crates/turborepo-lib/src/frameworks.json", - "utf-8" -); -const frameworks = JSON.parse(frameworksJsonString) as Array; - /** * Turborepo does some nice framework detection based on the dependencies in the package.json. This function ports that logic to this ESLint rule. * diff --git a/packages/eslint-plugin-turbo/package.json b/packages/eslint-plugin-turbo/package.json index a56dfd4d22375..1165f6c0525ea 100644 --- a/packages/eslint-plugin-turbo/package.json +++ b/packages/eslint-plugin-turbo/package.json @@ -19,6 +19,7 @@ }, "author": "Vercel", "main": "./dist/index.js", + "types": "./dist/index.d.ts", "files": [ "dist/**" ], diff --git a/packages/eslint-plugin-turbo/tsup.config.ts b/packages/eslint-plugin-turbo/tsup.config.ts index bbda8cb604ddc..12dc08af087d4 100644 --- a/packages/eslint-plugin-turbo/tsup.config.ts +++ b/packages/eslint-plugin-turbo/tsup.config.ts @@ -1,8 +1,9 @@ -import { defineConfig, Options } from "tsup"; +import { defineConfig, type Options } from "tsup"; export default defineConfig((options: Options) => ({ entry: ["lib/index.ts"], clean: true, minify: true, + dts: true, ...options, })); diff --git a/packages/turbo-benchmark/README.md b/packages/turbo-benchmark/README.md index 68946de6a3b46..2fe53013c516d 100644 --- a/packages/turbo-benchmark/README.md +++ b/packages/turbo-benchmark/README.md @@ -3,5 +3,5 @@ To run benchmarks for turborepo 1. Follow the [Building Turborepo](../CONTRIBUTING.md#building-turborepo) instructions to install dependencies -2. `cargo build -p turbo --profile release-turborepo` to build turbo +2. `cargo build --package turbo --profile release-turborepo` to build turbo 3. From this directory `pnpm run benchmark` diff --git a/packages/turbo-types/src/index.ts b/packages/turbo-types/src/index.ts index b6f907d11490b..99cef354039ec 100644 --- a/packages/turbo-types/src/index.ts +++ b/packages/turbo-types/src/index.ts @@ -1,3 +1,10 @@ +import type { Framework as FW } from "./types/frameworks"; +import frameworksJson from "./json/frameworks.json"; + +export const frameworks = frameworksJson as Array; +export type Framework = FW; +export type { FrameworkStrategy } from "./types/frameworks"; + export { type BaseSchema, type BaseSchema as BaseSchemaV2, diff --git a/crates/turborepo-lib/src/frameworks.json b/packages/turbo-types/src/json/frameworks.json similarity index 100% rename from crates/turborepo-lib/src/frameworks.json rename to packages/turbo-types/src/json/frameworks.json diff --git a/packages/turbo-types/src/types/frameworks.ts b/packages/turbo-types/src/types/frameworks.ts new file mode 100644 index 0000000000000..1f91e4f958474 --- /dev/null +++ b/packages/turbo-types/src/types/frameworks.ts @@ -0,0 +1,11 @@ +export type FrameworkStrategy = "all" | "some"; + +export interface Framework { + slug: string; + name: string; + envWildcards: Array; + dependencyMatch: { + strategy: FrameworkStrategy; + dependencies: Array; + }; +} From 99f25c4c269c8fa7ebf951dea97979080a0ba35a Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 5 Sep 2024 16:16:11 -0400 Subject: [PATCH 10/20] fix typo --- .../crafting-your-repository/using-environment-variables.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx b/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx index c994e55c3d888..aa3015494b854 100644 --- a/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx +++ b/docs/repo-docs/crafting-your-repository/using-environment-variables.mdx @@ -6,7 +6,7 @@ description: Learn how to handle environments for your applications. import { Callout } from '#/components/callout'; import { Tabs, Tab } from '#/components/tabs'; import { Accordion, Accordions } from '#/components/accordion'; -import { metadata } from 'eslint-plugin-turbo'; +import { frameworks } from '@turbo/types'; Environment variable inputs are a vital part of your applications that you'll need to account for in your Turborepo configuration. @@ -67,7 +67,7 @@ Turborepo automatically adds prefix wildcards to your [`env`](/repo/docs/referen - {metadata.frameworks.map(({ name, envWildcards }) => ( + {frameworks.map(({ name, envWildcards }) => ( {name} {envWildcards.map((w) => {w}).join(', ')} From 032720bc742d2537734dc5e6d667d887668db474 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 9 Sep 2024 16:21:22 -0400 Subject: [PATCH 11/20] split tests and memoize path joining --- .../no-undeclared-env-vars.commonjs.test.ts | 470 ++++++++ .../lib/no-undeclared-env-vars.module.test.ts | 470 ++++++++ .../lib/no-undeclared-env-vars.test.ts | 1074 ----------------- 3 files changed, 940 insertions(+), 1074 deletions(-) create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.commonjs.test.ts create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.module.test.ts delete mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.test.ts diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.commonjs.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.commonjs.test.ts new file mode 100644 index 0000000000000..628a376bd74be --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.commonjs.test.ts @@ -0,0 +1,470 @@ +import path from "node:path"; +import { RuleTester } from "eslint"; +import { RULES } from "../../lib/constants"; +import rule from "../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020 }, +}); + +const paths = { + workspaceConfigs: { + root: path.join(__dirname, "../../__fixtures__/workspace-configs"), + index: path.join( + __dirname, + "../../__fixtures__/workspace-configs/apps/web/index.js" + ), + }, + configs: { + root: path.join(__dirname, "../../__fixtures__/configs/single"), + }, +}; + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: ` + const env2 = process.env['ENV_2']; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const env2 = process.env["ENV_2"]; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { ENV_2 } = process.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { ROOT_DOT_ENV, WEB_DOT_ENV } = process.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { NEXT_PUBLIC_HAHAHAHA } = process.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { ENV_1 } = process.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { ENV_1 } = process.env; + `, + options: [{ cwd: "/some/random/path" }], + }, + { + code: ` + const { CI } = process.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = process.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const { NEW_STYLE_ENV_KEY, TASK_ENV_KEY } = process.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const { NEW_STYLE_GLOBAL_ENV_KEY, TASK_ENV_KEY } = process.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const val = process.env["NEW_STYLE_GLOBAL_ENV_KEY"]; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = process.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const x = process.env.GLOBAL_ENV_KEY; + const { TASK_ENV_KEY, GLOBAL_ENV_KEY: renamedX } = process.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: "var x = process.env.GLOBAL_ENV_KEY;", + options: [{ cwd: paths.configs.root }], + }, + { + code: "let x = process.env.TASK_ENV_KEY;", + options: [{ cwd: paths.configs.root }], + }, + { + code: "const x = process.env.ANOTHER_KEY_VALUE;", + options: [ + { + cwd: paths.configs.root, + allowList: ["^ANOTHER_KEY_[A-Z]+$"], + }, + ], + }, + { + code: ` + var x = process.env.ENV_VAR_ONE; + var y = process.env.ENV_VAR_TWO; + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + var x = process.env.ENV_VAR_ONE; + var y = process.env.ENV_VAR_TWO; + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_O[A-Z]+$", "ENV_VAR_TWO"], + }, + ], + }, + { + code: ` + var globalOrTask = process.env.TASK_ENV_KEY || process.env.GLOBAL_ENV_KEY; + var oneOrTwo = process.env.ENV_VAR_ONE || process.env.ENV_VAR_TWO; + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + () => { return process.env.GLOBAL_ENV_KEY } + () => { return process.env.TASK_ENV_KEY } + () => { return process.env.ENV_VAR_ALLOWED } + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + var foo = process?.env.GLOBAL_ENV_KEY + var foo = process?.env.TASK_ENV_KEY + var foo = process?.env.ENV_VAR_ALLOWED + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + function test(arg1 = process.env.GLOBAL_ENV_KEY) {}; + function test(arg1 = process.env.TASK_ENV_KEY) {}; + function test(arg1 = process.env.ENV_VAR_ALLOWED) {}; + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + (arg1 = process.env.GLOBAL_ENV_KEY) => {} + (arg1 = process.env.TASK_ENV_KEY) => {} + (arg1 = process.env.ENV_VAR_ALLOWED) => {} + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: "const getEnv = (key) => process.env[key];", + options: [{ cwd: paths.configs.root }], + }, + { + code: "function getEnv(key) { return process.env[key]; }", + options: [{ cwd: paths.configs.root }], + }, + { + code: "for (let x of ['ONE', 'TWO', 'THREE']) { console.log(process.env[x]); }", + options: [{ cwd: paths.configs.root }], + }, + ], + + invalid: [ + { + code: ` + const env2 = process.env['ENV_3']; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + errors: [ + { + message: + "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: ` + const env2 = process.env["ENV_3"]; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + errors: [ + { + message: + "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: ` + const { ENV_2 } = process.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: path.join( + __dirname, + "../../__fixtures__/workspace-configs/apps/docs/index.js" + ), + errors: [ + { + message: + "ENV_2 is not listed as a dependency in the root turbo.json or workspace (apps/docs) turbo.json", + }, + ], + }, + { + code: ` + const { NEXT_PUBLIC_HAHAHAHA, NEXT_PUBLIC_EXCLUDE, NEXT_PUBLIC_EXCLUDED } = process.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + errors: [ + { + message: + "NEXT_PUBLIC_EXCLUDE is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + { + message: + "NEXT_PUBLIC_EXCLUDED is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: "let { X } = process.env;", + options: [{ cwd: paths.configs.root }], + errors: [{ message: "X is not listed as a dependency in turbo.json" }], + }, + { + code: "const { X, Y, Z } = process.env;", + options: [{ cwd: paths.configs.root }], + errors: [ + { message: "X is not listed as a dependency in turbo.json" }, + { message: "Y is not listed as a dependency in turbo.json" }, + { message: "Z is not listed as a dependency in turbo.json" }, + ], + }, + { + code: "const { X, Y: NewName, Z } = process.env;", + options: [{ cwd: paths.configs.root }], + errors: [ + { message: "X is not listed as a dependency in turbo.json" }, + { message: "Y is not listed as a dependency in turbo.json" }, + { message: "Z is not listed as a dependency in turbo.json" }, + ], + }, + { + code: "var x = process.env.NOT_THERE;", + options: [{ cwd: paths.configs.root }], + errors: [ + { + message: "NOT_THERE is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: "var x = process.env.KEY;", + options: [ + { + cwd: paths.configs.root, + allowList: ["^ANOTHER_KEY_[A-Z]+$"], + }, + ], + errors: [{ message: "KEY is not listed as a dependency in turbo.json" }], + }, + { + code: ` + var globalOrTask = process.env.TASK_ENV_KEY_NEW || process.env.GLOBAL_ENV_KEY_NEW; + var oneOrTwo = process.env.ENV_VAR_ONE || process.env.ENV_VAR_TWO; + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: "ENV_VAR_ONE is not listed as a dependency in turbo.json", + }, + { + message: "ENV_VAR_TWO is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + () => { return process.env.GLOBAL_ENV_KEY_NEW } + () => { return process.env.TASK_ENV_KEY_NEW } + () => { return process.env.ENV_VAR_NOT_ALLOWED } + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + var foo = process?.env.GLOBAL_ENV_KEY_NEW + var foo = process?.env.TASK_ENV_KEY_NEW + var foo = process?.env.ENV_VAR_NOT_ALLOWED + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + function test(arg1 = process.env.GLOBAL_ENV_KEY_NEW) {}; + function test(arg1 = process.env.TASK_ENV_KEY_NEW) {}; + function test(arg1 = process.env.ENV_VAR_NOT_ALLOWED) {}; + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + (arg1 = process.env.GLOBAL_ENV_KEY_NEW) => {} + (arg1 = process.env.TASK_ENV_KEY_NEW) => {} + (arg1 = process.env.ENV_VAR_NOT_ALLOWED) => {} + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.module.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.module.test.ts new file mode 100644 index 0000000000000..ac23579e2276e --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.module.test.ts @@ -0,0 +1,470 @@ +import path from "node:path"; +import { RuleTester } from "eslint"; +import { RULES } from "../../lib/constants"; +import rule from "../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, +}); + +const paths = { + workspaceConfigs: { + root: path.join(__dirname, "../../__fixtures__/workspace-configs"), + index: path.join( + __dirname, + "../../__fixtures__/workspace-configs/apps/web/index.js" + ), + }, + configs: { + root: path.join(__dirname, "../../__fixtures__/configs/single"), + }, +}; + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: ` + const env2 = import.meta.env['ENV_2']; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const env2 = import.meta.env["ENV_2"]; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { ENV_2 } = import.meta.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { ROOT_DOT_ENV, WEB_DOT_ENV } = import.meta.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { NEXT_PUBLIC_HAHAHAHA } = import.meta.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { ENV_1 } = import.meta.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { ENV_1 } = import.meta.env; + `, + options: [{ cwd: "/some/random/path" }], + }, + { + code: ` + const { CI } = import.meta.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + }, + { + code: ` + const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = import.meta.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const { NEW_STYLE_ENV_KEY, TASK_ENV_KEY } = import.meta.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const { NEW_STYLE_GLOBAL_ENV_KEY, TASK_ENV_KEY } = import.meta.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const val = import.meta.env["NEW_STYLE_GLOBAL_ENV_KEY"]; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = import.meta.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: ` + const x = import.meta.env.GLOBAL_ENV_KEY; + const { TASK_ENV_KEY, GLOBAL_ENV_KEY: renamedX } = import.meta.env; + `, + options: [{ cwd: paths.configs.root }], + }, + { + code: "var x = import.meta.env.GLOBAL_ENV_KEY;", + options: [{ cwd: paths.configs.root }], + }, + { + code: "let x = import.meta.env.TASK_ENV_KEY;", + options: [{ cwd: paths.configs.root }], + }, + { + code: "const x = import.meta.env.ANOTHER_KEY_VALUE;", + options: [ + { + cwd: paths.configs.root, + allowList: ["^ANOTHER_KEY_[A-Z]+$"], + }, + ], + }, + { + code: ` + var x = import.meta.env.ENV_VAR_ONE; + var y = import.meta.env.ENV_VAR_TWO; + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + var x = import.meta.env.ENV_VAR_ONE; + var y = import.meta.env.ENV_VAR_TWO; + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_O[A-Z]+$", "ENV_VAR_TWO"], + }, + ], + }, + { + code: ` + var globalOrTask = import.meta.env.TASK_ENV_KEY || import.meta.env.GLOBAL_ENV_KEY; + var oneOrTwo = import.meta.env.ENV_VAR_ONE || import.meta.env.ENV_VAR_TWO; + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + () => { return import.meta.env.GLOBAL_ENV_KEY } + () => { return import.meta.env.TASK_ENV_KEY } + () => { return import.meta.env.ENV_VAR_ALLOWED } + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + var foo = process?.env.GLOBAL_ENV_KEY + var foo = process?.env.TASK_ENV_KEY + var foo = process?.env.ENV_VAR_ALLOWED + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + function test1(arg1 = import.meta.env.GLOBAL_ENV_KEY) {}; + function test2(arg1 = import.meta.env.TASK_ENV_KEY) {}; + function test3(arg1 = import.meta.env.ENV_VAR_ALLOWED) {}; + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: ` + (arg1 = import.meta.env.GLOBAL_ENV_KEY) => {} + (arg1 = import.meta.env.TASK_ENV_KEY) => {} + (arg1 = import.meta.env.ENV_VAR_ALLOWED) => {} + `, + options: [ + { + cwd: paths.configs.root, + allowList: ["^ENV_VAR_[A-Z]+$"], + }, + ], + }, + { + code: "const getEnv = (key) => import.meta.env[key];", + options: [{ cwd: paths.configs.root }], + }, + { + code: "function getEnv(key) { return import.meta.env[key]; }", + options: [{ cwd: paths.configs.root }], + }, + { + code: "for (let x of ['ONE', 'TWO', 'THREE']) { console.log(import.meta.env[x]); }", + options: [{ cwd: paths.configs.root }], + }, + ], + + invalid: [ + { + code: ` + const env2 = import.meta.env['ENV_3']; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + errors: [ + { + message: + "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: ` + const env2 = import.meta.env["ENV_3"]; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + errors: [ + { + message: + "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: ` + const { ENV_2 } = import.meta.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: path.join( + __dirname, + "../../__fixtures__/workspace-configs/apps/docs/index.js" + ), + errors: [ + { + message: + "ENV_2 is not listed as a dependency in the root turbo.json or workspace (apps/docs) turbo.json", + }, + ], + }, + { + code: ` + const { NEXT_PUBLIC_HAHAHAHA, NEXT_PUBLIC_EXCLUDE, NEXT_PUBLIC_EXCLUDED } = import.meta.env; + `, + options: [{ cwd: paths.workspaceConfigs.root }], + filename: paths.workspaceConfigs.index, + errors: [ + { + message: + "NEXT_PUBLIC_EXCLUDE is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + { + message: + "NEXT_PUBLIC_EXCLUDED is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: "let { X } = import.meta.env;", + options: [{ cwd: paths.configs.root }], + errors: [{ message: "X is not listed as a dependency in turbo.json" }], + }, + { + code: "const { X, Y, Z } = import.meta.env;", + options: [{ cwd: paths.configs.root }], + errors: [ + { message: "X is not listed as a dependency in turbo.json" }, + { message: "Y is not listed as a dependency in turbo.json" }, + { message: "Z is not listed as a dependency in turbo.json" }, + ], + }, + { + code: "const { X, Y: NewName, Z } = import.meta.env;", + options: [{ cwd: paths.configs.root }], + errors: [ + { message: "X is not listed as a dependency in turbo.json" }, + { message: "Y is not listed as a dependency in turbo.json" }, + { message: "Z is not listed as a dependency in turbo.json" }, + ], + }, + { + code: "var x = import.meta.env.NOT_THERE;", + options: [{ cwd: paths.configs.root }], + errors: [ + { + message: "NOT_THERE is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: "var x = import.meta.env.KEY;", + options: [ + { + cwd: paths.configs.root, + allowList: ["^ANOTHER_KEY_[A-Z]+$"], + }, + ], + errors: [{ message: "KEY is not listed as a dependency in turbo.json" }], + }, + { + code: ` + var globalOrTask = import.meta.env.TASK_ENV_KEY_NEW || import.meta.env.GLOBAL_ENV_KEY_NEW; + var oneOrTwo = import.meta.env.ENV_VAR_ONE || import.meta.env.ENV_VAR_TWO; + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: "ENV_VAR_ONE is not listed as a dependency in turbo.json", + }, + { + message: "ENV_VAR_TWO is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + () => { return import.meta.env.GLOBAL_ENV_KEY_NEW } + () => { return import.meta.env.TASK_ENV_KEY_NEW } + () => { return import.meta.env.ENV_VAR_NOT_ALLOWED } + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + var foo = process?.env.GLOBAL_ENV_KEY_NEW + var foo = process?.env.TASK_ENV_KEY_NEW + var foo = process?.env.ENV_VAR_NOT_ALLOWED + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + function test1(arg1 = import.meta.env.GLOBAL_ENV_KEY_NEW) {}; + function test2(arg1 = import.meta.env.TASK_ENV_KEY_NEW) {}; + function test3(arg1 = import.meta.env.ENV_VAR_NOT_ALLOWED) {}; + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + (arg1 = import.meta.env.GLOBAL_ENV_KEY_NEW) => {} + (arg1 = import.meta.env.TASK_ENV_KEY_NEW) => {} + (arg1 = import.meta.env.ENV_VAR_NOT_ALLOWED) => {} + `, + options: [ + { + cwd: paths.configs.root, + }, + ], + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.test.ts deleted file mode 100644 index 5e912fdae4876..0000000000000 --- a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.test.ts +++ /dev/null @@ -1,1074 +0,0 @@ -import path from "node:path"; -import { RuleTester } from "eslint"; -import { RULES } from "../../lib/constants"; -import rule from "../../lib/rules/no-undeclared-env-vars"; - -const ruleTester = new RuleTester({ - parserOptions: { ecmaVersion: 2020 }, -}); - -const moduleRuleTester = new RuleTester({ - parserOptions: { ecmaVersion: 2020, sourceType: "module" }, -}); - -ruleTester.run(RULES.noUndeclaredEnvVars, rule, { - valid: [ - { - code: ` - const env2 = process.env['ENV_2']; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const env2 = process.env["ENV_2"]; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { ENV_2 } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { ROOT_DOT_ENV, WEB_DOT_ENV } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { NEXT_PUBLIC_HAHAHAHA } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { ENV_1 } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { ENV_1 } = process.env; - `, - options: [{ cwd: "/some/random/path" }], - }, - { - code: ` - const { CI } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const { NEW_STYLE_ENV_KEY, TASK_ENV_KEY } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const { NEW_STYLE_GLOBAL_ENV_KEY, TASK_ENV_KEY } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const val = process.env["NEW_STYLE_GLOBAL_ENV_KEY"]; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const x = process.env.GLOBAL_ENV_KEY; - const { TASK_ENV_KEY, GLOBAL_ENV_KEY: renamedX } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "var x = process.env.GLOBAL_ENV_KEY;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "let x = process.env.TASK_ENV_KEY;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "const x = process.env.ANOTHER_KEY_VALUE;", - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ANOTHER_KEY_[A-Z]+$"], - }, - ], - }, - { - code: ` - var x = process.env.ENV_VAR_ONE; - var y = process.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - var x = process.env.ENV_VAR_ONE; - var y = process.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_O[A-Z]+$", "ENV_VAR_TWO"], - }, - ], - }, - { - code: ` - var globalOrTask = process.env.TASK_ENV_KEY || process.env.GLOBAL_ENV_KEY; - var oneOrTwo = process.env.ENV_VAR_ONE || process.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - () => { return process.env.GLOBAL_ENV_KEY } - () => { return process.env.TASK_ENV_KEY } - () => { return process.env.ENV_VAR_ALLOWED } - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - var foo = process?.env.GLOBAL_ENV_KEY - var foo = process?.env.TASK_ENV_KEY - var foo = process?.env.ENV_VAR_ALLOWED - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - function test(arg1 = process.env.GLOBAL_ENV_KEY) {}; - function test(arg1 = process.env.TASK_ENV_KEY) {}; - function test(arg1 = process.env.ENV_VAR_ALLOWED) {}; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - (arg1 = process.env.GLOBAL_ENV_KEY) => {} - (arg1 = process.env.TASK_ENV_KEY) => {} - (arg1 = process.env.ENV_VAR_ALLOWED) => {} - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: "const getEnv = (key) => process.env[key];", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "function getEnv(key) { return process.env[key]; }", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "for (let x of ['ONE', 'TWO', 'THREE']) { console.log(process.env[x]); }", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - ], - - invalid: [ - { - code: ` - const env2 = process.env['ENV_3']; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - errors: [ - { - message: - "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: ` - const env2 = process.env["ENV_3"]; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - errors: [ - { - message: - "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: ` - const { ENV_2 } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/docs/index.js" - ), - errors: [ - { - message: - "ENV_2 is not listed as a dependency in the root turbo.json or workspace (apps/docs) turbo.json", - }, - ], - }, - { - code: ` - const { NEXT_PUBLIC_HAHAHAHA, NEXT_PUBLIC_EXCLUDE, NEXT_PUBLIC_EXCLUDED } = process.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - errors: [ - { - message: - "NEXT_PUBLIC_EXCLUDE is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - { - message: - "NEXT_PUBLIC_EXCLUDED is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: "let { X } = process.env;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - errors: [{ message: "X is not listed as a dependency in turbo.json" }], - }, - { - code: "const { X, Y, Z } = process.env;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - errors: [ - { message: "X is not listed as a dependency in turbo.json" }, - { message: "Y is not listed as a dependency in turbo.json" }, - { message: "Z is not listed as a dependency in turbo.json" }, - ], - }, - { - code: "const { X, Y: NewName, Z } = process.env;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - errors: [ - { message: "X is not listed as a dependency in turbo.json" }, - { message: "Y is not listed as a dependency in turbo.json" }, - { message: "Z is not listed as a dependency in turbo.json" }, - ], - }, - { - code: "var x = process.env.NOT_THERE;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - errors: [ - { - message: "NOT_THERE is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: "var x = process.env.KEY;", - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ANOTHER_KEY_[A-Z]+$"], - }, - ], - errors: [{ message: "KEY is not listed as a dependency in turbo.json" }], - }, - { - code: ` - var globalOrTask = process.env.TASK_ENV_KEY_NEW || process.env.GLOBAL_ENV_KEY_NEW; - var oneOrTwo = process.env.ENV_VAR_ONE || process.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: "ENV_VAR_ONE is not listed as a dependency in turbo.json", - }, - { - message: "ENV_VAR_TWO is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - () => { return process.env.GLOBAL_ENV_KEY_NEW } - () => { return process.env.TASK_ENV_KEY_NEW } - () => { return process.env.ENV_VAR_NOT_ALLOWED } - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - var foo = process?.env.GLOBAL_ENV_KEY_NEW - var foo = process?.env.TASK_ENV_KEY_NEW - var foo = process?.env.ENV_VAR_NOT_ALLOWED - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - function test(arg1 = process.env.GLOBAL_ENV_KEY_NEW) {}; - function test(arg1 = process.env.TASK_ENV_KEY_NEW) {}; - function test(arg1 = process.env.ENV_VAR_NOT_ALLOWED) {}; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - (arg1 = process.env.GLOBAL_ENV_KEY_NEW) => {} - (arg1 = process.env.TASK_ENV_KEY_NEW) => {} - (arg1 = process.env.ENV_VAR_NOT_ALLOWED) => {} - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - ], -}); - -moduleRuleTester.run(RULES.noUndeclaredEnvVars, rule, { - valid: [ - { - code: ` - const env2 = import.meta.env['ENV_2']; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const env2 = import.meta.env["ENV_2"]; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { ENV_2 } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { ROOT_DOT_ENV, WEB_DOT_ENV } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { NEXT_PUBLIC_HAHAHAHA } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { ENV_1 } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { ENV_1 } = import.meta.env; - `, - options: [{ cwd: "/some/random/path" }], - }, - { - code: ` - const { CI } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - { - code: ` - const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const { NEW_STYLE_ENV_KEY, TASK_ENV_KEY } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const { NEW_STYLE_GLOBAL_ENV_KEY, TASK_ENV_KEY } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const val = import.meta.env["NEW_STYLE_GLOBAL_ENV_KEY"]; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: ` - const x = import.meta.env.GLOBAL_ENV_KEY; - const { TASK_ENV_KEY, GLOBAL_ENV_KEY: renamedX } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "var x = import.meta.env.GLOBAL_ENV_KEY;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "let x = import.meta.env.TASK_ENV_KEY;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "const x = import.meta.env.ANOTHER_KEY_VALUE;", - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ANOTHER_KEY_[A-Z]+$"], - }, - ], - }, - { - code: ` - var x = import.meta.env.ENV_VAR_ONE; - var y = import.meta.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - var x = import.meta.env.ENV_VAR_ONE; - var y = import.meta.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_O[A-Z]+$", "ENV_VAR_TWO"], - }, - ], - }, - { - code: ` - var globalOrTask = import.meta.env.TASK_ENV_KEY || import.meta.env.GLOBAL_ENV_KEY; - var oneOrTwo = import.meta.env.ENV_VAR_ONE || import.meta.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - () => { return import.meta.env.GLOBAL_ENV_KEY } - () => { return import.meta.env.TASK_ENV_KEY } - () => { return import.meta.env.ENV_VAR_ALLOWED } - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - var foo = process?.env.GLOBAL_ENV_KEY - var foo = process?.env.TASK_ENV_KEY - var foo = process?.env.ENV_VAR_ALLOWED - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - function test1(arg1 = import.meta.env.GLOBAL_ENV_KEY) {}; - function test2(arg1 = import.meta.env.TASK_ENV_KEY) {}; - function test3(arg1 = import.meta.env.ENV_VAR_ALLOWED) {}; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - (arg1 = import.meta.env.GLOBAL_ENV_KEY) => {} - (arg1 = import.meta.env.TASK_ENV_KEY) => {} - (arg1 = import.meta.env.ENV_VAR_ALLOWED) => {} - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: "const getEnv = (key) => import.meta.env[key];", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "function getEnv(key) { return import.meta.env[key]; }", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - { - code: "for (let x of ['ONE', 'TWO', 'THREE']) { console.log(import.meta.env[x]); }", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - }, - ], - - invalid: [ - { - code: ` - const env2 = import.meta.env['ENV_3']; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - errors: [ - { - message: - "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: ` - const env2 = import.meta.env["ENV_3"]; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - errors: [ - { - message: - "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: ` - const { ENV_2 } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/docs/index.js" - ), - errors: [ - { - message: - "ENV_2 is not listed as a dependency in the root turbo.json or workspace (apps/docs) turbo.json", - }, - ], - }, - { - code: ` - const { NEXT_PUBLIC_HAHAHAHA, NEXT_PUBLIC_EXCLUDE, NEXT_PUBLIC_EXCLUDED } = import.meta.env; - `, - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/workspace-configs") }, - ], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - errors: [ - { - message: - "NEXT_PUBLIC_EXCLUDE is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - { - message: - "NEXT_PUBLIC_EXCLUDED is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: "let { X } = import.meta.env;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - errors: [{ message: "X is not listed as a dependency in turbo.json" }], - }, - { - code: "const { X, Y, Z } = import.meta.env;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - errors: [ - { message: "X is not listed as a dependency in turbo.json" }, - { message: "Y is not listed as a dependency in turbo.json" }, - { message: "Z is not listed as a dependency in turbo.json" }, - ], - }, - { - code: "const { X, Y: NewName, Z } = import.meta.env;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - errors: [ - { message: "X is not listed as a dependency in turbo.json" }, - { message: "Y is not listed as a dependency in turbo.json" }, - { message: "Z is not listed as a dependency in turbo.json" }, - ], - }, - { - code: "var x = import.meta.env.NOT_THERE;", - options: [ - { cwd: path.join(__dirname, "../../__fixtures__/configs/single") }, - ], - errors: [ - { - message: "NOT_THERE is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: "var x = import.meta.env.KEY;", - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - allowList: ["^ANOTHER_KEY_[A-Z]+$"], - }, - ], - errors: [{ message: "KEY is not listed as a dependency in turbo.json" }], - }, - { - code: ` - var globalOrTask = import.meta.env.TASK_ENV_KEY_NEW || import.meta.env.GLOBAL_ENV_KEY_NEW; - var oneOrTwo = import.meta.env.ENV_VAR_ONE || import.meta.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: "ENV_VAR_ONE is not listed as a dependency in turbo.json", - }, - { - message: "ENV_VAR_TWO is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - () => { return import.meta.env.GLOBAL_ENV_KEY_NEW } - () => { return import.meta.env.TASK_ENV_KEY_NEW } - () => { return import.meta.env.ENV_VAR_NOT_ALLOWED } - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - var foo = process?.env.GLOBAL_ENV_KEY_NEW - var foo = process?.env.TASK_ENV_KEY_NEW - var foo = process?.env.ENV_VAR_NOT_ALLOWED - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - function test1(arg1 = import.meta.env.GLOBAL_ENV_KEY_NEW) {}; - function test2(arg1 = import.meta.env.TASK_ENV_KEY_NEW) {}; - function test3(arg1 = import.meta.env.ENV_VAR_NOT_ALLOWED) {}; - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - (arg1 = import.meta.env.GLOBAL_ENV_KEY_NEW) => {} - (arg1 = import.meta.env.TASK_ENV_KEY_NEW) => {} - (arg1 = import.meta.env.ENV_VAR_NOT_ALLOWED) => {} - `, - options: [ - { - cwd: path.join(__dirname, "../../__fixtures__/configs/single"), - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - ], -}); From c3d3e8f884c882b75e38b39dc477ff0ecc249222 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 9 Sep 2024 17:38:54 -0400 Subject: [PATCH 12/20] organizes tests --- .../eslint-plugin-turbo/__tests__/cwd.test.ts | 22 +- .../no-undeclared-env-vars.commonjs.test.ts | 470 ------------------ .../no-undeclared-env-vars.commonjs.test.ts | 306 ++++++++++++ .../no-undeclared-env-vars.module.test.ts | 280 +++-------- .../no-undeclared-env-vars.commonjs.test.ts | 18 + .../no-undeclared-env-vars.module.test.ts | 18 + .../no-undeclared-env-vars.commonjs.test.ts | 132 +++++ .../no-undeclared-env-vars.module.test.ts | 132 +++++ .../lib/rules/no-undeclared-env-vars.ts | 5 +- 9 files changed, 680 insertions(+), 703 deletions(-) delete mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.commonjs.test.ts create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/configs/no-undeclared-env-vars.commonjs.test.ts rename packages/eslint-plugin-turbo/__tests__/lib/{ => no-undeclared-env-vars/configs}/no-undeclared-env-vars.module.test.ts (53%) create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.commonjs.test.ts create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.module.test.ts create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/workspace-configs/no-undeclared-env-vars.commonjs.test.ts create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/workspace-configs/no-undeclared-env-vars.module.test.ts diff --git a/packages/eslint-plugin-turbo/__tests__/cwd.test.ts b/packages/eslint-plugin-turbo/__tests__/cwd.test.ts index c7c5079d6c3d6..3901a1cbcd512 100644 --- a/packages/eslint-plugin-turbo/__tests__/cwd.test.ts +++ b/packages/eslint-plugin-turbo/__tests__/cwd.test.ts @@ -1,7 +1,7 @@ -import path from "path"; -import JSON5 from "json5"; -import { execSync } from "child_process"; -import { Schema } from "@turbo/types"; +import path from "node:path"; +import { execSync } from "node:child_process"; +import { type Schema } from "@turbo/types"; +import { parse, stringify } from "json5"; import { setupTestFixtures } from "@turbo/test-utils"; describe("eslint settings check", () => { @@ -17,7 +17,7 @@ describe("eslint settings check", () => { cwd, encoding: "utf8", }); - const configJson = JSON5.parse(configString); + const configJson: Record = parse(configString); expect(configJson.settings).toEqual({ turbo: { @@ -77,7 +77,7 @@ describe("eslint settings check", () => { encoding: "utf8", } ); - const configJson = JSON5.parse(configString); + const configJson: Record = parse(configString); expect(configJson.settings).toEqual({ turbo: { @@ -144,8 +144,10 @@ describe("eslint cache is busted", () => { cwd, encoding: "utf8", }); - } catch (error: any) { - const outputJson = JSON5.parse(error.stdout); + } catch (error: unknown) { + const outputJson: Record = parse( + (error as { stdout: string }).stdout + ); expect(outputJson).toMatchObject([ { messages: [ @@ -162,7 +164,7 @@ describe("eslint cache is busted", () => { const turboJson = readJson("turbo.json"); if (turboJson && "globalEnv" in turboJson) { turboJson.globalEnv = ["CI", "NONEXISTENT"]; - write("turbo.json", JSON5.stringify(turboJson, null, 2)); + write("turbo.json", stringify(turboJson, null, 2)); } // test that we invalidated the eslint cache @@ -170,7 +172,7 @@ describe("eslint cache is busted", () => { cwd, encoding: "utf8", }); - const outputJson = JSON5.parse(output); + const outputJson: Record = parse(output); expect(outputJson).toMatchObject([{ errorCount: 0 }]); }); }); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.commonjs.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.commonjs.test.ts deleted file mode 100644 index 628a376bd74be..0000000000000 --- a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.commonjs.test.ts +++ /dev/null @@ -1,470 +0,0 @@ -import path from "node:path"; -import { RuleTester } from "eslint"; -import { RULES } from "../../lib/constants"; -import rule from "../../lib/rules/no-undeclared-env-vars"; - -const ruleTester = new RuleTester({ - parserOptions: { ecmaVersion: 2020 }, -}); - -const paths = { - workspaceConfigs: { - root: path.join(__dirname, "../../__fixtures__/workspace-configs"), - index: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - configs: { - root: path.join(__dirname, "../../__fixtures__/configs/single"), - }, -}; - -ruleTester.run(RULES.noUndeclaredEnvVars, rule, { - valid: [ - { - code: ` - const env2 = process.env['ENV_2']; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const env2 = process.env["ENV_2"]; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { ENV_2 } = process.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { ROOT_DOT_ENV, WEB_DOT_ENV } = process.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { NEXT_PUBLIC_HAHAHAHA } = process.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { ENV_1 } = process.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { ENV_1 } = process.env; - `, - options: [{ cwd: "/some/random/path" }], - }, - { - code: ` - const { CI } = process.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = process.env; - `, - options: [{ cwd: paths.configs.root }], - }, - { - code: ` - const { NEW_STYLE_ENV_KEY, TASK_ENV_KEY } = process.env; - `, - options: [{ cwd: paths.configs.root }], - }, - { - code: ` - const { NEW_STYLE_GLOBAL_ENV_KEY, TASK_ENV_KEY } = process.env; - `, - options: [{ cwd: paths.configs.root }], - }, - { - code: ` - const val = process.env["NEW_STYLE_GLOBAL_ENV_KEY"]; - `, - options: [{ cwd: paths.configs.root }], - }, - { - code: ` - const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = process.env; - `, - options: [{ cwd: paths.configs.root }], - }, - { - code: ` - const x = process.env.GLOBAL_ENV_KEY; - const { TASK_ENV_KEY, GLOBAL_ENV_KEY: renamedX } = process.env; - `, - options: [{ cwd: paths.configs.root }], - }, - { - code: "var x = process.env.GLOBAL_ENV_KEY;", - options: [{ cwd: paths.configs.root }], - }, - { - code: "let x = process.env.TASK_ENV_KEY;", - options: [{ cwd: paths.configs.root }], - }, - { - code: "const x = process.env.ANOTHER_KEY_VALUE;", - options: [ - { - cwd: paths.configs.root, - allowList: ["^ANOTHER_KEY_[A-Z]+$"], - }, - ], - }, - { - code: ` - var x = process.env.ENV_VAR_ONE; - var y = process.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - var x = process.env.ENV_VAR_ONE; - var y = process.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_O[A-Z]+$", "ENV_VAR_TWO"], - }, - ], - }, - { - code: ` - var globalOrTask = process.env.TASK_ENV_KEY || process.env.GLOBAL_ENV_KEY; - var oneOrTwo = process.env.ENV_VAR_ONE || process.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - () => { return process.env.GLOBAL_ENV_KEY } - () => { return process.env.TASK_ENV_KEY } - () => { return process.env.ENV_VAR_ALLOWED } - `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - var foo = process?.env.GLOBAL_ENV_KEY - var foo = process?.env.TASK_ENV_KEY - var foo = process?.env.ENV_VAR_ALLOWED - `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - function test(arg1 = process.env.GLOBAL_ENV_KEY) {}; - function test(arg1 = process.env.TASK_ENV_KEY) {}; - function test(arg1 = process.env.ENV_VAR_ALLOWED) {}; - `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: ` - (arg1 = process.env.GLOBAL_ENV_KEY) => {} - (arg1 = process.env.TASK_ENV_KEY) => {} - (arg1 = process.env.ENV_VAR_ALLOWED) => {} - `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], - }, - { - code: "const getEnv = (key) => process.env[key];", - options: [{ cwd: paths.configs.root }], - }, - { - code: "function getEnv(key) { return process.env[key]; }", - options: [{ cwd: paths.configs.root }], - }, - { - code: "for (let x of ['ONE', 'TWO', 'THREE']) { console.log(process.env[x]); }", - options: [{ cwd: paths.configs.root }], - }, - ], - - invalid: [ - { - code: ` - const env2 = process.env['ENV_3']; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - errors: [ - { - message: - "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: ` - const env2 = process.env["ENV_3"]; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - errors: [ - { - message: - "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: ` - const { ENV_2 } = process.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/docs/index.js" - ), - errors: [ - { - message: - "ENV_2 is not listed as a dependency in the root turbo.json or workspace (apps/docs) turbo.json", - }, - ], - }, - { - code: ` - const { NEXT_PUBLIC_HAHAHAHA, NEXT_PUBLIC_EXCLUDE, NEXT_PUBLIC_EXCLUDED } = process.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - errors: [ - { - message: - "NEXT_PUBLIC_EXCLUDE is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - { - message: - "NEXT_PUBLIC_EXCLUDED is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: "let { X } = process.env;", - options: [{ cwd: paths.configs.root }], - errors: [{ message: "X is not listed as a dependency in turbo.json" }], - }, - { - code: "const { X, Y, Z } = process.env;", - options: [{ cwd: paths.configs.root }], - errors: [ - { message: "X is not listed as a dependency in turbo.json" }, - { message: "Y is not listed as a dependency in turbo.json" }, - { message: "Z is not listed as a dependency in turbo.json" }, - ], - }, - { - code: "const { X, Y: NewName, Z } = process.env;", - options: [{ cwd: paths.configs.root }], - errors: [ - { message: "X is not listed as a dependency in turbo.json" }, - { message: "Y is not listed as a dependency in turbo.json" }, - { message: "Z is not listed as a dependency in turbo.json" }, - ], - }, - { - code: "var x = process.env.NOT_THERE;", - options: [{ cwd: paths.configs.root }], - errors: [ - { - message: "NOT_THERE is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: "var x = process.env.KEY;", - options: [ - { - cwd: paths.configs.root, - allowList: ["^ANOTHER_KEY_[A-Z]+$"], - }, - ], - errors: [{ message: "KEY is not listed as a dependency in turbo.json" }], - }, - { - code: ` - var globalOrTask = process.env.TASK_ENV_KEY_NEW || process.env.GLOBAL_ENV_KEY_NEW; - var oneOrTwo = process.env.ENV_VAR_ONE || process.env.ENV_VAR_TWO; - `, - options: [ - { - cwd: paths.configs.root, - }, - ], - errors: [ - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: "ENV_VAR_ONE is not listed as a dependency in turbo.json", - }, - { - message: "ENV_VAR_TWO is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - () => { return process.env.GLOBAL_ENV_KEY_NEW } - () => { return process.env.TASK_ENV_KEY_NEW } - () => { return process.env.ENV_VAR_NOT_ALLOWED } - `, - options: [ - { - cwd: paths.configs.root, - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - var foo = process?.env.GLOBAL_ENV_KEY_NEW - var foo = process?.env.TASK_ENV_KEY_NEW - var foo = process?.env.ENV_VAR_NOT_ALLOWED - `, - options: [ - { - cwd: paths.configs.root, - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - function test(arg1 = process.env.GLOBAL_ENV_KEY_NEW) {}; - function test(arg1 = process.env.TASK_ENV_KEY_NEW) {}; - function test(arg1 = process.env.ENV_VAR_NOT_ALLOWED) {}; - `, - options: [ - { - cwd: paths.configs.root, - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - { - code: ` - (arg1 = process.env.GLOBAL_ENV_KEY_NEW) => {} - (arg1 = process.env.TASK_ENV_KEY_NEW) => {} - (arg1 = process.env.ENV_VAR_NOT_ALLOWED) => {} - `, - options: [ - { - cwd: paths.configs.root, - }, - ], - errors: [ - { - message: - "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", - }, - { - message: - "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/configs/no-undeclared-env-vars.commonjs.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/configs/no-undeclared-env-vars.commonjs.test.ts new file mode 100644 index 0000000000000..b778eb2688895 --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/configs/no-undeclared-env-vars.commonjs.test.ts @@ -0,0 +1,306 @@ +import path from "node:path"; +import { RuleTester } from "eslint"; +import { RULES } from "../../../../lib/constants"; +import rule from "../../../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020 }, +}); + +const cwd = path.join(__dirname, "../../../../__fixtures__/configs/single"); +const options = (extra: Record = {}) => ({ + options: [ + { + cwd, + ...extra, + }, + ], +}); + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: ` + const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = process.env; + `, + ...options(), + }, + { + code: ` + const { NEW_STYLE_ENV_KEY, TASK_ENV_KEY } = process.env; + `, + ...options(), + }, + { + code: ` + const { NEW_STYLE_GLOBAL_ENV_KEY, TASK_ENV_KEY } = process.env; + `, + ...options(), + }, + { + code: ` + const val = process.env["NEW_STYLE_GLOBAL_ENV_KEY"]; + `, + ...options(), + }, + { + code: ` + const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = process.env; + `, + ...options(), + }, + { + code: ` + const x = process.env.GLOBAL_ENV_KEY; + const { TASK_ENV_KEY, GLOBAL_ENV_KEY: renamedX } = process.env; + `, + ...options(), + }, + { + code: "var x = process.env.GLOBAL_ENV_KEY;", + ...options(), + }, + { + code: "let x = process.env.TASK_ENV_KEY;", + ...options(), + }, + { + code: "const x = process.env.ANOTHER_KEY_VALUE;", + ...options({ + allowList: ["^ANOTHER_KEY_[A-Z]+$"], + }), + }, + { + code: ` + var x = process.env.ENV_VAR_ONE; + var y = process.env.ENV_VAR_TWO; + `, + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), + }, + { + code: ` + var x = process.env.ENV_VAR_ONE; + var y = process.env.ENV_VAR_TWO; + `, + ...options({ + allowList: ["^ENV_VAR_O[A-Z]+$", "ENV_VAR_TWO"], + }), + }, + { + code: ` + var globalOrTask = process.env.TASK_ENV_KEY || process.env.GLOBAL_ENV_KEY; + var oneOrTwo = process.env.ENV_VAR_ONE || process.env.ENV_VAR_TWO; + `, + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), + }, + { + code: ` + () => { return process.env.GLOBAL_ENV_KEY } + () => { return process.env.TASK_ENV_KEY } + () => { return process.env.ENV_VAR_ALLOWED } + `, + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), + }, + { + code: ` + var foo = process?.env.GLOBAL_ENV_KEY + var foo = process?.env.TASK_ENV_KEY + var foo = process?.env.ENV_VAR_ALLOWED + `, + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), + }, + { + code: ` + function test(arg1 = process.env.GLOBAL_ENV_KEY) {}; + function test(arg1 = process.env.TASK_ENV_KEY) {}; + function test(arg1 = process.env.ENV_VAR_ALLOWED) {}; + `, + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), + }, + { + code: ` + (arg1 = process.env.GLOBAL_ENV_KEY) => {} + (arg1 = process.env.TASK_ENV_KEY) => {} + (arg1 = process.env.ENV_VAR_ALLOWED) => {} + `, + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), + }, + { + code: "const getEnv = (key) => process.env[key];", + ...options(), + }, + { + code: "function getEnv(key) { return process.env[key]; }", + ...options(), + }, + { + code: "for (let x of ['ONE', 'TWO', 'THREE']) { console.log(process.env[x]); }", + ...options(), + }, + ], + invalid: [ + { + code: "let { X } = process.env;", + ...options(), + errors: [{ message: "X is not listed as a dependency in turbo.json" }], + }, + { + code: "const { X, Y, Z } = process.env;", + ...options(), + errors: [ + { message: "X is not listed as a dependency in turbo.json" }, + { message: "Y is not listed as a dependency in turbo.json" }, + { message: "Z is not listed as a dependency in turbo.json" }, + ], + }, + { + code: "const { X, Y: NewName, Z } = process.env;", + ...options(), + errors: [ + { message: "X is not listed as a dependency in turbo.json" }, + { message: "Y is not listed as a dependency in turbo.json" }, + { message: "Z is not listed as a dependency in turbo.json" }, + ], + }, + { + code: "var x = process.env.NOT_THERE;", + ...options(), + errors: [ + { + message: "NOT_THERE is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: "var x = process.env.KEY;", + ...options({ + allowList: ["^ANOTHER_KEY_[A-Z]+$"], + }), + + errors: [{ message: "KEY is not listed as a dependency in turbo.json" }], + }, + { + code: ` + var globalOrTask = process.env.TASK_ENV_KEY_NEW || process.env.GLOBAL_ENV_KEY_NEW; + var oneOrTwo = process.env.ENV_VAR_ONE || process.env.ENV_VAR_TWO; + `, + ...options(), + errors: [ + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: "ENV_VAR_ONE is not listed as a dependency in turbo.json", + }, + { + message: "ENV_VAR_TWO is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + () => { return process.env.GLOBAL_ENV_KEY_NEW } + () => { return process.env.TASK_ENV_KEY_NEW } + () => { return process.env.ENV_VAR_NOT_ALLOWED } + `, + ...options(), + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + var foo = process?.env.GLOBAL_ENV_KEY_NEW + var foo = process?.env.TASK_ENV_KEY_NEW + var foo = process?.env.ENV_VAR_NOT_ALLOWED + `, + ...options(), + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + function test(arg1 = process.env.GLOBAL_ENV_KEY_NEW) {}; + function test(arg1 = process.env.TASK_ENV_KEY_NEW) {}; + function test(arg1 = process.env.ENV_VAR_NOT_ALLOWED) {}; + `, + ...options(), + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: ` + (arg1 = process.env.GLOBAL_ENV_KEY_NEW) => {} + (arg1 = process.env.TASK_ENV_KEY_NEW) => {} + (arg1 = process.env.ENV_VAR_NOT_ALLOWED) => {} + `, + ...options(), + errors: [ + { + message: + "GLOBAL_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "TASK_ENV_KEY_NEW is not listed as a dependency in turbo.json", + }, + { + message: + "ENV_VAR_NOT_ALLOWED is not listed as a dependency in turbo.json", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.module.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/configs/no-undeclared-env-vars.module.test.ts similarity index 53% rename from packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.module.test.ts rename to packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/configs/no-undeclared-env-vars.module.test.ts index ac23579e2276e..f6d58306b4b55 100644 --- a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars.module.test.ts +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/configs/no-undeclared-env-vars.module.test.ts @@ -1,171 +1,101 @@ import path from "node:path"; import { RuleTester } from "eslint"; -import { RULES } from "../../lib/constants"; -import rule from "../../lib/rules/no-undeclared-env-vars"; +import { RULES } from "../../../../lib/constants"; +import rule from "../../../../lib/rules/no-undeclared-env-vars"; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020, sourceType: "module" }, }); -const paths = { - workspaceConfigs: { - root: path.join(__dirname, "../../__fixtures__/workspace-configs"), - index: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/web/index.js" - ), - }, - configs: { - root: path.join(__dirname, "../../__fixtures__/configs/single"), - }, -}; +const cwd = path.join(__dirname, "../../../../__fixtures__/configs/single"); +const options = (extra: Record = {}) => ({ + options: [ + { + cwd, + ...extra, + }, + ], +}); ruleTester.run(RULES.noUndeclaredEnvVars, rule, { valid: [ - { - code: ` - const env2 = import.meta.env['ENV_2']; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const env2 = import.meta.env["ENV_2"]; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { ENV_2 } = import.meta.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { ROOT_DOT_ENV, WEB_DOT_ENV } = import.meta.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { NEXT_PUBLIC_HAHAHAHA } = import.meta.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { ENV_1 } = import.meta.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, - { - code: ` - const { ENV_1 } = import.meta.env; - `, - options: [{ cwd: "/some/random/path" }], - }, - { - code: ` - const { CI } = import.meta.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - }, { code: ` const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = import.meta.env; `, - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: ` const { NEW_STYLE_ENV_KEY, TASK_ENV_KEY } = import.meta.env; `, - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: ` const { NEW_STYLE_GLOBAL_ENV_KEY, TASK_ENV_KEY } = import.meta.env; `, - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: ` const val = import.meta.env["NEW_STYLE_GLOBAL_ENV_KEY"]; `, - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: ` const { TASK_ENV_KEY, ANOTHER_ENV_KEY } = import.meta.env; `, - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: ` const x = import.meta.env.GLOBAL_ENV_KEY; const { TASK_ENV_KEY, GLOBAL_ENV_KEY: renamedX } = import.meta.env; `, - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: "var x = import.meta.env.GLOBAL_ENV_KEY;", - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: "let x = import.meta.env.TASK_ENV_KEY;", - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: "const x = import.meta.env.ANOTHER_KEY_VALUE;", - options: [ - { - cwd: paths.configs.root, - allowList: ["^ANOTHER_KEY_[A-Z]+$"], - }, - ], + ...options({ + allowList: ["^ANOTHER_KEY_[A-Z]+$"], + }), }, { code: ` var x = import.meta.env.ENV_VAR_ONE; var y = import.meta.env.ENV_VAR_TWO; `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), }, { code: ` var x = import.meta.env.ENV_VAR_ONE; var y = import.meta.env.ENV_VAR_TWO; `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_O[A-Z]+$", "ENV_VAR_TWO"], - }, - ], + ...options({ + allowList: ["^ENV_VAR_O[A-Z]+$", "ENV_VAR_TWO"], + }), }, { code: ` var globalOrTask = import.meta.env.TASK_ENV_KEY || import.meta.env.GLOBAL_ENV_KEY; var oneOrTwo = import.meta.env.ENV_VAR_ONE || import.meta.env.ENV_VAR_TWO; `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), }, { code: ` @@ -173,12 +103,9 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { () => { return import.meta.env.TASK_ENV_KEY } () => { return import.meta.env.ENV_VAR_ALLOWED } `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), }, { code: ` @@ -186,12 +113,9 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { var foo = process?.env.TASK_ENV_KEY var foo = process?.env.ENV_VAR_ALLOWED `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), }, { code: ` @@ -199,12 +123,9 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { function test2(arg1 = import.meta.env.TASK_ENV_KEY) {}; function test3(arg1 = import.meta.env.ENV_VAR_ALLOWED) {}; `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), }, { code: ` @@ -212,95 +133,33 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { (arg1 = import.meta.env.TASK_ENV_KEY) => {} (arg1 = import.meta.env.ENV_VAR_ALLOWED) => {} `, - options: [ - { - cwd: paths.configs.root, - allowList: ["^ENV_VAR_[A-Z]+$"], - }, - ], + ...options({ + allowList: ["^ENV_VAR_[A-Z]+$"], + }), }, { code: "const getEnv = (key) => import.meta.env[key];", - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: "function getEnv(key) { return import.meta.env[key]; }", - options: [{ cwd: paths.configs.root }], + ...options(), }, { code: "for (let x of ['ONE', 'TWO', 'THREE']) { console.log(import.meta.env[x]); }", - options: [{ cwd: paths.configs.root }], + ...options(), }, ], invalid: [ - { - code: ` - const env2 = import.meta.env['ENV_3']; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - errors: [ - { - message: - "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: ` - const env2 = import.meta.env["ENV_3"]; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - errors: [ - { - message: - "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, - { - code: ` - const { ENV_2 } = import.meta.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: path.join( - __dirname, - "../../__fixtures__/workspace-configs/apps/docs/index.js" - ), - errors: [ - { - message: - "ENV_2 is not listed as a dependency in the root turbo.json or workspace (apps/docs) turbo.json", - }, - ], - }, - { - code: ` - const { NEXT_PUBLIC_HAHAHAHA, NEXT_PUBLIC_EXCLUDE, NEXT_PUBLIC_EXCLUDED } = import.meta.env; - `, - options: [{ cwd: paths.workspaceConfigs.root }], - filename: paths.workspaceConfigs.index, - errors: [ - { - message: - "NEXT_PUBLIC_EXCLUDE is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - { - message: - "NEXT_PUBLIC_EXCLUDED is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", - }, - ], - }, { code: "let { X } = import.meta.env;", - options: [{ cwd: paths.configs.root }], + ...options(), errors: [{ message: "X is not listed as a dependency in turbo.json" }], }, { code: "const { X, Y, Z } = import.meta.env;", - options: [{ cwd: paths.configs.root }], + ...options(), errors: [ { message: "X is not listed as a dependency in turbo.json" }, { message: "Y is not listed as a dependency in turbo.json" }, @@ -309,7 +168,7 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { }, { code: "const { X, Y: NewName, Z } = import.meta.env;", - options: [{ cwd: paths.configs.root }], + ...options(), errors: [ { message: "X is not listed as a dependency in turbo.json" }, { message: "Y is not listed as a dependency in turbo.json" }, @@ -318,7 +177,7 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { }, { code: "var x = import.meta.env.NOT_THERE;", - options: [{ cwd: paths.configs.root }], + ...options(), errors: [ { message: "NOT_THERE is not listed as a dependency in turbo.json", @@ -327,12 +186,9 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { }, { code: "var x = import.meta.env.KEY;", - options: [ - { - cwd: paths.configs.root, - allowList: ["^ANOTHER_KEY_[A-Z]+$"], - }, - ], + ...options({ + allowList: ["^ANOTHER_KEY_[A-Z]+$"], + }), errors: [{ message: "KEY is not listed as a dependency in turbo.json" }], }, { @@ -340,11 +196,7 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { var globalOrTask = import.meta.env.TASK_ENV_KEY_NEW || import.meta.env.GLOBAL_ENV_KEY_NEW; var oneOrTwo = import.meta.env.ENV_VAR_ONE || import.meta.env.ENV_VAR_TWO; `, - options: [ - { - cwd: paths.configs.root, - }, - ], + ...options(), errors: [ { message: @@ -368,11 +220,7 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { () => { return import.meta.env.TASK_ENV_KEY_NEW } () => { return import.meta.env.ENV_VAR_NOT_ALLOWED } `, - options: [ - { - cwd: paths.configs.root, - }, - ], + ...options(), errors: [ { message: @@ -394,11 +242,7 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { var foo = process?.env.TASK_ENV_KEY_NEW var foo = process?.env.ENV_VAR_NOT_ALLOWED `, - options: [ - { - cwd: paths.configs.root, - }, - ], + ...options(), errors: [ { message: @@ -420,11 +264,7 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { function test2(arg1 = import.meta.env.TASK_ENV_KEY_NEW) {}; function test3(arg1 = import.meta.env.ENV_VAR_NOT_ALLOWED) {}; `, - options: [ - { - cwd: paths.configs.root, - }, - ], + ...options(), errors: [ { message: @@ -446,11 +286,7 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { (arg1 = import.meta.env.TASK_ENV_KEY_NEW) => {} (arg1 = import.meta.env.ENV_VAR_NOT_ALLOWED) => {} `, - options: [ - { - cwd: paths.configs.root, - }, - ], + ...options(), errors: [ { message: diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.commonjs.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.commonjs.test.ts new file mode 100644 index 0000000000000..412b133ad6242 --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.commonjs.test.ts @@ -0,0 +1,18 @@ +import { RuleTester } from "eslint"; +import { RULES } from "../../../lib/constants"; +import rule from "../../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020 }, +}); + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: ` + const { ENV_1 } = process.env; + `, + options: [{ cwd: "/some/random/path" }], + }, + ], +}); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.module.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.module.test.ts new file mode 100644 index 0000000000000..c0077640d9574 --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.module.test.ts @@ -0,0 +1,18 @@ +import { RuleTester } from "eslint"; +import { RULES } from "../../../lib/constants"; +import rule from "../../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, +}); + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: ` + const { ENV_1 } = import.meta.env; + `, + options: [{ cwd: "/some/random/path" }], + }, + ], +}); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/workspace-configs/no-undeclared-env-vars.commonjs.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/workspace-configs/no-undeclared-env-vars.commonjs.test.ts new file mode 100644 index 0000000000000..de6129ae2b92a --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/workspace-configs/no-undeclared-env-vars.commonjs.test.ts @@ -0,0 +1,132 @@ +import path from "node:path"; +import { RuleTester } from "eslint"; +import { RULES } from "../../../../lib/constants"; +import rule from "../../../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020 }, +}); + +const cwd = path.join(__dirname, "../../../../__fixtures__/workspace-configs"); +const webFilename = path.join(cwd, "/apps/web/index.js"); +const docsFilename = path.join(cwd, "/apps/docs/index.js"); +const options = (extra: Record = {}) => ({ + options: [ + { + cwd, + ...extra, + }, + ], +}); + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: ` + const env2 = process.env['ENV_2']; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const env2 = process.env["ENV_2"]; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { ENV_2 } = process.env; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { ROOT_DOT_ENV, WEB_DOT_ENV } = process.env; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { NEXT_PUBLIC_HAHAHAHA } = process.env; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { ENV_1 } = process.env; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { CI } = process.env; + `, + ...options(), + filename: webFilename, + }, + ], + invalid: [ + { + code: ` + const env2 = process.env['ENV_3']; + `, + ...options(), + filename: webFilename, + errors: [ + { + message: + "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: ` + const env2 = process.env["ENV_3"]; + `, + ...options(), + filename: webFilename, + errors: [ + { + message: + "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: ` + const { ENV_2 } = process.env; + `, + ...options(), + filename: docsFilename, + errors: [ + { + message: + "ENV_2 is not listed as a dependency in the root turbo.json or workspace (apps/docs) turbo.json", + }, + ], + }, + { + code: ` + const { NEXT_PUBLIC_HAHAHAHA, NEXT_PUBLIC_EXCLUDE, NEXT_PUBLIC_EXCLUDED } = process.env; + `, + ...options(), + filename: webFilename, + errors: [ + { + message: + "NEXT_PUBLIC_EXCLUDE is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + { + message: + "NEXT_PUBLIC_EXCLUDED is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/workspace-configs/no-undeclared-env-vars.module.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/workspace-configs/no-undeclared-env-vars.module.test.ts new file mode 100644 index 0000000000000..5ca66e6b4761f --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/workspace-configs/no-undeclared-env-vars.module.test.ts @@ -0,0 +1,132 @@ +import path from "node:path"; +import { RuleTester } from "eslint"; +import { RULES } from "../../../../lib/constants"; +import rule from "../../../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, +}); + +const cwd = path.join(__dirname, "../../../../__fixtures__/workspace-configs"); +const webFilename = path.join(cwd, "/apps/web/index.js"); +const docsFilename = path.join(cwd, "/apps/docs/index.js"); +const options = (extra: Record = {}) => ({ + options: [ + { + cwd, + ...extra, + }, + ], +}); + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: ` + const env2 = import.meta.env['ENV_2']; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const env2 = import.meta.env["ENV_2"]; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { ENV_2 } = import.meta.env; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { ROOT_DOT_ENV, WEB_DOT_ENV } = import.meta.env; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { NEXT_PUBLIC_HAHAHAHA } = import.meta.env; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { ENV_1 } = import.meta.env; + `, + ...options(), + filename: webFilename, + }, + { + code: ` + const { CI } = import.meta.env; + `, + ...options(), + filename: webFilename, + }, + ], + invalid: [ + { + code: ` + const env2 = import.meta.env['ENV_3']; + `, + ...options(), + filename: webFilename, + errors: [ + { + message: + "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: ` + const env2 = import.meta.env["ENV_3"]; + `, + ...options(), + filename: webFilename, + errors: [ + { + message: + "ENV_3 is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + { + code: ` + const { ENV_2 } = import.meta.env; + `, + ...options(), + filename: docsFilename, + errors: [ + { + message: + "ENV_2 is not listed as a dependency in the root turbo.json or workspace (apps/docs) turbo.json", + }, + ], + }, + { + code: ` + const { NEXT_PUBLIC_HAHAHAHA, NEXT_PUBLIC_EXCLUDE, NEXT_PUBLIC_EXCLUDED } = import.meta.env; + `, + ...options(), + filename: webFilename, + errors: [ + { + message: + "NEXT_PUBLIC_EXCLUDE is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + { + message: + "NEXT_PUBLIC_EXCLUDED is not listed as a dependency in the root turbo.json or workspace (apps/web) turbo.json", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index 6f05384dacf78..0632cd71c50c4 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -150,7 +150,10 @@ const frameworkEnvMatches = (filePath: string): Set => { : searchDependencies.some(hasDependency); if (hasMatch) { - return new Set([...acc, ...envWildcards.map(RegExp)]); + return new Set([ + ...acc, + ...envWildcards.map((envWildcard) => RegExp(envWildcard)), + ]); } return acc; }, From 4cf7840879aa17ad4aa41981da81e999bfa7c9f5 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 9 Sep 2024 17:39:09 -0400 Subject: [PATCH 13/20] adds tests for framework inference --- .../no-undeclared-env-vars.commonjs.test.ts | 61 +++++++++++++++++++ .../no-undeclared-env-vars.module.test.ts | 61 +++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.commonjs.test.ts create mode 100644 packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.module.test.ts diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.commonjs.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.commonjs.test.ts new file mode 100644 index 0000000000000..b15d918e86100 --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.commonjs.test.ts @@ -0,0 +1,61 @@ +import path from "node:path"; +import { RuleTester } from "eslint"; +import { RULES } from "../../../../lib/constants"; +import rule from "../../../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020 }, +}); + +const cwd = path.join( + __dirname, + "../../../../__fixtures__/framework-inference" +); +const nextJsFilename = path.join(cwd, "/apps/nextjs/index.js"); +const viteFilename = path.join(cwd, "/apps/vite/index.js"); +const options = (extra: Record = {}) => ({ + options: [ + { + cwd, + ...extra, + }, + ], +}); + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: `const { NEXT_PUBLIC_ZILTOID } = process.env;`, + ...options(), + filename: nextJsFilename, + }, + { + code: `const { VITE_THINGS } = process.env;`, + ...options(), + filename: viteFilename, + }, + ], + invalid: [ + { + code: `const { NEXT_PUBLIC_ZILTOID } = process.env;`, + ...options(), + filename: viteFilename, + errors: [ + { + message: + "NEXT_PUBLIC_ZILTOID is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: `const { VITE_THINGS } = process.env;`, + ...options(), + filename: nextJsFilename, + errors: [ + { + message: "VITE_THINGS is not listed as a dependency in turbo.json", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.module.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.module.test.ts new file mode 100644 index 0000000000000..5cf443f4dcbe4 --- /dev/null +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.module.test.ts @@ -0,0 +1,61 @@ +import path from "node:path"; +import { RuleTester } from "eslint"; +import { RULES } from "../../../../lib/constants"; +import rule from "../../../../lib/rules/no-undeclared-env-vars"; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, +}); + +const cwd = path.join( + __dirname, + "../../../../__fixtures__/framework-inference" +); +const nextJsFilename = path.join(cwd, "/apps/nextjs/index.js"); +const viteFilename = path.join(cwd, "/apps/vite/index.js"); +const options = (extra: Record = {}) => ({ + options: [ + { + cwd, + ...extra, + }, + ], +}); + +ruleTester.run(RULES.noUndeclaredEnvVars, rule, { + valid: [ + { + code: `const { NEXT_PUBLIC_ZILTOID } = import.meta.env;`, + ...options(), + filename: nextJsFilename, + }, + { + code: `const { VITE_THINGS } = import.meta.env;`, + ...options(), + filename: viteFilename, + }, + ], + invalid: [ + { + code: `const { NEXT_PUBLIC_ZILTOID } = import.meta.env;`, + ...options(), + filename: viteFilename, + errors: [ + { + message: + "NEXT_PUBLIC_ZILTOID is not listed as a dependency in turbo.json", + }, + ], + }, + { + code: `const { VITE_THINGS } = import.meta.env;`, + ...options(), + filename: nextJsFilename, + errors: [ + { + message: "VITE_THINGS is not listed as a dependency in turbo.json", + }, + ], + }, + ], +}); From a08cbcf3aa176a39fe4b7bde197729a515f527ef Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 9 Sep 2024 17:52:40 -0400 Subject: [PATCH 14/20] use searchUp instead --- .../lib/rules/no-undeclared-env-vars.ts | 36 +++++-------------- packages/turbo-utils/src/searchUp.ts | 10 ++++++ 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index 0632cd71c50c4..78621ce02fc5a 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -1,8 +1,8 @@ import path from "node:path"; -import { existsSync, readFileSync } from "node:fs"; +import { readFileSync } from "node:fs"; import type { Rule } from "eslint"; import type { Node, MemberExpression } from "estree"; -import { logger } from "@turbo/utils"; +import { logger, searchUp } from "@turbo/utils"; import { frameworks } from "@turbo/types"; import { RULES } from "../constants"; import { Project, getWorkspaceFromFilePath } from "../utils/calculate-inputs"; @@ -22,28 +22,6 @@ export interface RuleContextWithOptions extends Rule.RuleContext { }>; } -/** recursively find the closest package.json from the given directory */ -const findClosestPackageJson = (currentDir: string): string | null => { - debug(`searching for package.json in ${currentDir}`); - const packageJsonPath = path.join(currentDir, "package.json"); - - // Check if package.json exists in the current directory - if (existsSync(packageJsonPath)) { - return packageJsonPath; - } - - // Get the parent directory - const parentDir = path.dirname(currentDir); - - // If we've reached the root directory, stop searching - if (parentDir === currentDir) { - return null; - } - - // Recursively search in the parent directory - return findClosestPackageJson(parentDir); -}; - const meta: Rule.RuleMetaData = { type: "problem", docs: { @@ -125,14 +103,16 @@ const packageJsonDependencies = (filePath: string): Set => { */ const frameworkEnvMatches = (filePath: string): Set => { const directory = path.dirname(filePath); - const packageJsonPath = findClosestPackageJson(directory); - if (!packageJsonPath) { + const packageJsonDir = searchUp({ cwd: directory, target: "package.json" }); + if (!packageJsonDir) { logger.error(`No package.json found connected to ${filePath}`); return new Set(); } - debug(`found package.json: ${packageJsonPath}`); + debug(`found package.json in: ${packageJsonDir}`); - const dependencies = packageJsonDependencies(packageJsonPath); + const dependencies = packageJsonDependencies( + `${packageJsonDir}/package.json` + ); const hasDependency = (dep: string) => dependencies.has(dep); debug(`dependencies for ${filePath}: ${Array.from(dependencies).join(",")}`); diff --git a/packages/turbo-utils/src/searchUp.ts b/packages/turbo-utils/src/searchUp.ts index 0c5c4b7eb77dd..8b9de5bc5afcc 100644 --- a/packages/turbo-utils/src/searchUp.ts +++ b/packages/turbo-utils/src/searchUp.ts @@ -1,13 +1,23 @@ import fs from "node:fs"; import path from "node:path"; +/** + * recursively search up the file tree looking for a `target` file, starting with the provided `cwd` + * + * If found, return the directory containing the file. If not found, return null. + */ export function searchUp({ target, cwd, contentCheck, }: { + /** The name of the file we're looking for */ target: string; + + /** The directory to start the search */ cwd: string; + + /** a predicate for examining the content of any found file */ contentCheck?: (content: string) => boolean; }): string | null { const root = path.parse(cwd).root; From 6065fb549f1a25fbfe6c6359cbd8b1ba66a88826 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 9 Sep 2024 17:53:37 -0400 Subject: [PATCH 15/20] Update packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts Co-authored-by: Thomas Knickman --- .../eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index 78621ce02fc5a..06dab5a3a7acd 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -105,7 +105,7 @@ const frameworkEnvMatches = (filePath: string): Set => { const directory = path.dirname(filePath); const packageJsonDir = searchUp({ cwd: directory, target: "package.json" }); if (!packageJsonDir) { - logger.error(`No package.json found connected to ${filePath}`); + logger.error(`Could not determine package for ${filePath}`); return new Set(); } debug(`found package.json in: ${packageJsonDir}`); From 13f15dcc09edc6b7891ce78e09066fe37233353b Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 9 Sep 2024 18:19:43 -0400 Subject: [PATCH 16/20] adds some kitcken-sink tests --- .../apps/kitchen-sink/index.js | 3 +++ .../apps/kitchen-sink/package.json | 19 +++++++++++++++++++ .../no-undeclared-env-vars.commonjs.test.ts | 16 ++++++++++++++++ .../no-undeclared-env-vars.module.test.ts | 16 ++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/kitchen-sink/index.js create mode 100644 packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/kitchen-sink/package.json diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/kitchen-sink/index.js b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/kitchen-sink/index.js new file mode 100644 index 0000000000000..006cf9b8c8179 --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/kitchen-sink/index.js @@ -0,0 +1,3 @@ +process.env.NEXT_PUBLIC_ZILTOID; +process.env.GATSBY_THE; +process.env.NITRO_OMNISCIENT; diff --git a/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/kitchen-sink/package.json b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/kitchen-sink/package.json new file mode 100644 index 0000000000000..86930291d732c --- /dev/null +++ b/packages/eslint-plugin-turbo/__fixtures__/framework-inference/apps/kitchen-sink/package.json @@ -0,0 +1,19 @@ +{ + "name": "nextjs", + "dependencies": { + "next": "*", + "blitz": "*", + "react": "*", + "left-pad": "*", + "event-stream": "*", + "gatsby": "*", + "is-promise": "*", + "@faker-js/faker": "*", + "ua-parser-js": "*", + "nitropack": "*" + }, + "devDependencies": { + "eslint": "8.57.0", + "eslint-plugin-turbo": "../../../../" + } +} diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.commonjs.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.commonjs.test.ts index b15d918e86100..2e48a404e8e6b 100644 --- a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.commonjs.test.ts +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.commonjs.test.ts @@ -13,6 +13,7 @@ const cwd = path.join( ); const nextJsFilename = path.join(cwd, "/apps/nextjs/index.js"); const viteFilename = path.join(cwd, "/apps/vite/index.js"); +const kitchenSinkFilename = path.join(cwd, "/apps/kitchen-sink/index.js"); const options = (extra: Record = {}) => ({ options: [ { @@ -34,6 +35,11 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { ...options(), filename: viteFilename, }, + { + code: `const { NEXT_PUBLIC_ZILTOID, GATSBY_THE, NITRO_OMNISCIENT } = process.env;`, + ...options(), + filename: kitchenSinkFilename, + }, ], invalid: [ { @@ -57,5 +63,15 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { }, ], }, + { + code: `const { VITE_THINGS } = process.env;`, + ...options(), + filename: kitchenSinkFilename, + errors: [ + { + message: "VITE_THINGS is not listed as a dependency in turbo.json", + }, + ], + }, ], }); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.module.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.module.test.ts index 5cf443f4dcbe4..bd8ab7737b122 100644 --- a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.module.test.ts +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/framework-inference/no-undeclared-env-vars.module.test.ts @@ -13,6 +13,7 @@ const cwd = path.join( ); const nextJsFilename = path.join(cwd, "/apps/nextjs/index.js"); const viteFilename = path.join(cwd, "/apps/vite/index.js"); +const kitchenSinkFilename = path.join(cwd, "/apps/kitchen-sink/index.js"); const options = (extra: Record = {}) => ({ options: [ { @@ -34,6 +35,11 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { ...options(), filename: viteFilename, }, + { + code: `const { NEXT_PUBLIC_ZILTOID, GATSBY_THE, NITRO_OMNISCIENT } = import.meta.env;`, + ...options(), + filename: kitchenSinkFilename, + }, ], invalid: [ { @@ -57,5 +63,15 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { }, ], }, + { + code: `const { VITE_THINGS } = import.meta.env;`, + ...options(), + filename: kitchenSinkFilename, + errors: [ + { + message: "VITE_THINGS is not listed as a dependency in turbo.json", + }, + ], + }, ], }); From 6ed760a83a2926ef795283a716d4bd1be129a0db Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 9 Sep 2024 18:37:25 -0400 Subject: [PATCH 17/20] adds more error handling for file read and parsing --- .../lib/rules/no-undeclared-env-vars.ts | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index 06dab5a3a7acd..33074b35b8fd2 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -2,7 +2,7 @@ import path from "node:path"; import { readFileSync } from "node:fs"; import type { Rule } from "eslint"; import type { Node, MemberExpression } from "estree"; -import { logger, searchUp } from "@turbo/utils"; +import { type PackageJson, logger, searchUp } from "@turbo/utils"; import { frameworks } from "@turbo/types"; import { RULES } from "../constants"; import { Project, getWorkspaceFromFilePath } from "../utils/calculate-inputs"; @@ -80,18 +80,31 @@ function normalizeCwd( /** for a given `package.json` file path, this will compile a Set of that package's listed dependencies */ const packageJsonDependencies = (filePath: string): Set => { // get the contents of the package.json - const packageJsonString = readFileSync(filePath, "utf-8"); - const packageJson = JSON.parse(packageJsonString) as Record< - string, - undefined | Record - >; - - return [ - "dependencies", - "devDependencies", - "peerDependencies", - // intentionally not including `optionalDependencies` or `bundleDependencies` because at the time of writing they are not used for any of the frameworks we support - ] + let packageJsonString; + + try { + packageJsonString = readFileSync(filePath, "utf-8"); + } catch (e) { + logger.error(`Could not read package.json at ${filePath}`); + return new Set(); + } + + let packageJson: PackageJson; + try { + packageJson = JSON.parse(packageJsonString) as PackageJson; + } catch (e) { + logger.error(`Could not parse package.json at ${filePath}`); + return new Set(); + } + + return ( + [ + "dependencies", + "devDependencies", + "peerDependencies", + // intentionally not including `optionalDependencies` or `bundleDependencies` because at the time of writing they are not used for any of the frameworks we support + ] as const + ) .flatMap((key) => Object.keys(packageJson[key] ?? {})) .reduce((acc, dependency) => acc.add(dependency), new Set()); }; From 73ba5936efa1270d1f335aad196935e6f2110ee4 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Tue, 10 Sep 2024 16:09:29 -0400 Subject: [PATCH 18/20] turns off debugging, also uses RUNNER_DEBUG --- .../eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts index 33074b35b8fd2..7b49d59afd8ee 100644 --- a/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts +++ b/packages/eslint-plugin-turbo/lib/rules/no-undeclared-env-vars.ts @@ -7,9 +7,7 @@ import { frameworks } from "@turbo/types"; import { RULES } from "../constants"; import { Project, getWorkspaceFromFilePath } from "../utils/calculate-inputs"; -/** set this to true if debugging this rule */ -const debugging = "".length === 0; -const debug = debugging +const debug = process.env.RUNNER_DEBUG ? logger.info : (_: string) => { /* noop */ From f3c37300a6026fef108e3dc0faac98969cbe2131 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 16 Sep 2024 18:07:56 -0400 Subject: [PATCH 19/20] updates base tests --- .../no-undeclared-env-vars.commonjs.test.ts | 7 +++++++ .../no-undeclared-env-vars.module.test.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.commonjs.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.commonjs.test.ts index 412b133ad6242..81e40b3b79a00 100644 --- a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.commonjs.test.ts +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.commonjs.test.ts @@ -8,6 +8,12 @@ const ruleTester = new RuleTester({ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { valid: [ + { + code: ` + const { TZ } = process.env; + `, + options: [{ cwd: "/some/random/path" }], + }, { code: ` const { ENV_1 } = process.env; @@ -15,4 +21,5 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { options: [{ cwd: "/some/random/path" }], }, ], + invalid: [], }); diff --git a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.module.test.ts b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.module.test.ts index c0077640d9574..ee75c05f50d24 100644 --- a/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.module.test.ts +++ b/packages/eslint-plugin-turbo/__tests__/lib/no-undeclared-env-vars/no-undeclared-env-vars.module.test.ts @@ -8,6 +8,12 @@ const ruleTester = new RuleTester({ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { valid: [ + { + code: ` + const { TZ } = import.meta.env; + `, + options: [{ cwd: "/some/random/path" }], + }, { code: ` const { ENV_1 } = import.meta.env; @@ -15,4 +21,5 @@ ruleTester.run(RULES.noUndeclaredEnvVars, rule, { options: [{ cwd: "/some/random/path" }], }, ], + invalid: [], }); From d22005009fb600fb58f83d3ec5131760701fa82e Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Mon, 16 Sep 2024 21:19:56 -0400 Subject: [PATCH 20/20] alphabetize and case-check brand names --- packages/turbo-types/src/json/frameworks.json | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/packages/turbo-types/src/json/frameworks.json b/packages/turbo-types/src/json/frameworks.json index 3c02611fe999a..f89c82e79824b 100644 --- a/packages/turbo-types/src/json/frameworks.json +++ b/packages/turbo-types/src/json/frameworks.json @@ -1,31 +1,4 @@ [ - { - "slug": "blitzjs", - "name": "Blitz.js", - "envWildcards": ["NEXT_PUBLIC_*"], - "dependencyMatch": { - "strategy": "all", - "dependencies": ["blitz"] - } - }, - { - "slug": "nextjs", - "name": "Next.js", - "envWildcards": ["NEXT_PUBLIC_*"], - "dependencyMatch": { - "strategy": "all", - "dependencies": ["next"] - } - }, - { - "slug": "gatsby", - "name": "Gatsby", - "envWildcards": ["GATSBY_*"], - "dependencyMatch": { - "strategy": "all", - "dependencies": ["gatsby"] - } - }, { "slug": "astro", "name": "Astro", @@ -36,39 +9,39 @@ } }, { - "slug": "solidstart", - "name": "SolidStart", - "envWildcards": ["VITE_*"], + "slug": "blitzjs", + "name": "Blitz", + "envWildcards": ["NEXT_PUBLIC_*"], "dependencyMatch": { "strategy": "all", - "dependencies": ["solid-js", "solid-start"] + "dependencies": ["blitz"] } }, { - "slug": "vue", - "name": "Vue", - "envWildcards": ["VUE_APP_*"], + "slug": "create-react-app", + "name": "Create React App", + "envWildcards": ["REACT_APP_*"], "dependencyMatch": { - "strategy": "all", - "dependencies": ["@vue/cli-service"] + "strategy": "some", + "dependencies": ["react-scripts", "react-dev-utils"] } }, { - "slug": "sveltekit", - "name": "SvelteKit", - "envWildcards": ["VITE_*", "PUBLIC_*"], + "slug": "gatsby", + "name": "Gatsby", + "envWildcards": ["GATSBY_*"], "dependencyMatch": { "strategy": "all", - "dependencies": ["@sveltejs/kit"] + "dependencies": ["gatsby"] } }, { - "slug": "create-react-app", - "name": "Create React App", - "envWildcards": ["REACT_APP_*"], + "slug": "nextjs", + "name": "Next.js", + "envWildcards": ["NEXT_PUBLIC_*"], "dependencyMatch": { - "strategy": "some", - "dependencies": ["react-scripts", "react-dev-utils"] + "strategy": "all", + "dependencies": ["next"] } }, { @@ -91,13 +64,40 @@ }, { "slug": "redwoodjs", - "name": "Redwood.js", + "name": "RedwoodJS", "envWildcards": ["REDWOOD_ENV_*"], "dependencyMatch": { "strategy": "all", "dependencies": ["@redwoodjs/core"] } }, + { + "slug": "sanity", + "name": "Sanity Studio", + "envWildcards": ["SANITY_STUDIO_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["@sanity/cli"] + } + }, + { + "slug": "solidstart", + "name": "Solid", + "envWildcards": ["VITE_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["solid-js", "solid-start"] + } + }, + { + "slug": "sveltekit", + "name": "SvelteKit", + "envWildcards": ["VITE_*", "PUBLIC_*"], + "dependencyMatch": { + "strategy": "all", + "dependencies": ["@sveltejs/kit"] + } + }, { "slug": "vite", "name": "Vite", @@ -108,12 +108,12 @@ } }, { - "slug": "sanity", - "name": "Sanity", - "envWildcards": ["SANITY_STUDIO_*"], + "slug": "vue", + "name": "Vue", + "envWildcards": ["VUE_APP_*"], "dependencyMatch": { "strategy": "all", - "dependencies": ["@sanity/cli"] + "dependencies": ["@vue/cli-service"] } } ]