Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e13b384
Merge pull request #9 from QwikDev/build/v2
JerryWu1234 Apr 11, 2025
94e47d7
fix(repl): improve deepUpdate logic for array item matching
JerryWu1234 Apr 11, 2025
69dd8fb
fix it
JerryWu1234 Apr 22, 2025
765ce4a
Merge branch 'QwikDev:build/v2' into build/v2
JerryWu1234 May 12, 2025
64d00a0
Merge branch 'QwikDev:build/v2' into build/v2
JerryWu1234 Jun 6, 2025
e729fdb
chore: update devDependencies and configurations
JerryWu1234 Jun 6, 2025
bfd176b
fix: init a test
JerryWu1234 Jun 6, 2025
d206cf6
FIX: fix edge case
JerryWu1234 Jun 9, 2025
07740ac
revert code
JerryWu1234 Jun 9, 2025
63cdda0
add change
JerryWu1234 Jun 12, 2025
8514061
Merge branch 'build/v2' into qwik-plugin-check
JerryWu1234 Jun 12, 2025
eaaf3ec
Merge branch 'build/v2' into qwik-plugin-check
JerryWu1234 Jun 26, 2025
275ed8a
fix: enhance eslint-plugin to accurately detect Node API usage and im…
JerryWu1234 Jun 26, 2025
cde161b
fix: refactor GLOBALAPIS to use array instead of Set and streamline d…
JerryWu1234 Jun 26, 2025
3e304c3
fix: improve handling of undeclared variables in scopeUseTask rule
JerryWu1234 Jun 26, 2025
de415bb
Merge branch 'build/v2' into qwik-plugin-check
JerryWu1234 Jul 17, 2025
9c25caa
fix: improve variable scope handling in scopeUseTask rule
JerryWu1234 Jul 29, 2025
c936c82
Merge branch 'build/v2' into qwik-plugin-check
gioboa Sep 15, 2025
3565578
Merge branch 'build/v2' into qwik-plugin-check
JerryWu1234 Oct 27, 2025
9ae3250
Merge branch 'build/v2' into qwik-plugin-check
JerryWu1234 Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/loud-mammals-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-qwik': patch
---

FIX: eslint-plugin: detect node API usage more accurately
47 changes: 13 additions & 34 deletions packages/eslint-plugin-qwik/src/scope-use-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Rule } from 'eslint';
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils';
import * as eslint from 'eslint'; // For Scope types
const ISSERVER = 'isServer';
const GLOBALAPIS = ['process', '__dirname', '__filename', 'module'];
const PRESETNODEAPIS = ['fs', 'os', 'path', 'child_process', 'http', 'https', 'Buffer'];
// Helper function: checks if a node is a descendant of another node
function isNodeDescendantOf(descendantNode, ancestorNode): boolean {
if (!ancestorNode) {
Expand Down Expand Up @@ -35,18 +37,7 @@ export const scopeUseTask: Rule.RuleModule = {
forbiddenApis: {
type: 'array',
items: { type: 'string' },
default: [
'process',
'fs',
'os',
'path',
'child_process',
'http',
'https',
'Buffer',
'__dirname',
'__filename',
],
default: PRESETNODEAPIS,
},
},
additionalProperties: false,
Expand All @@ -62,18 +53,7 @@ export const scopeUseTask: Rule.RuleModule = {
create(context: Rule.RuleContext): Rule.RuleListener {
const options = context.options[0] || {};
const forbiddenApis = new Set<string>(
options.forbiddenApis || [
'process',
'fs',
'os',
'path',
'child_process',
'http',
'https',
'Buffer',
'__dirname',
'__filename',
]
options.forbiddenApis || PRESETNODEAPIS.concat(GLOBALAPIS)
);
const serverGuardIdentifier: string = ISSERVER;
const sourceCode = context.sourceCode;
Expand All @@ -93,7 +73,6 @@ export const scopeUseTask: Rule.RuleModule = {
*/
function isApiUsageGuarded(apiOrCallNode, functionContextNode): boolean {
let currentParentNode: TSESTree.Node | undefined = apiOrCallNode.parent;

while (
currentParentNode &&
currentParentNode !== functionContextNode.body &&
Expand Down Expand Up @@ -161,6 +140,11 @@ export const scopeUseTask: Rule.RuleModule = {

// Try to find the variable starting from the current scope and going upwards
let currentScopeForSearch: eslint.Scope.Scope | null = scope;

if (!GLOBALAPIS.includes(identifierNode.name)) {
return true;
}

while (currentScopeForSearch) {
const foundVar = currentScopeForSearch.variables.find(
(v) => v.name === identifierNode.name
Expand All @@ -178,29 +162,25 @@ export const scopeUseTask: Rule.RuleModule = {
currentScopeForSearch = currentScopeForSearch.upper;
}

if (!variable) {
// Cannot find variable, assume it's not a shadowed global for safety,
// though this state implies an undeclared variable (another ESLint rule should catch this).
return false;
}
// If we didn't find a variable, it might be a global API or an undeclared variable.

if (variable.defs.length === 0) {
if (!variable || variable.defs.length === 0) {
// No definitions usually means it's an implicit global (e.g., 'process' in Node.js environment).
// Such a variable is NOT considered "shadowed by a user declaration".
return false;
}

// If there are definitions, check if any of them are standard declaration types.
// This means the identifier refers to a user-declared variable, parameter, function, class, or an import.
return variable.defs.some((def) => {
return variable?.defs.some((def) => {
return (
def.type === 'Variable' ||
def.type === 'Parameter' ||
def.type === 'FunctionName' ||
def.type === 'ClassName' ||
def.type === 'ImportBinding'
);
});
}) as boolean;
}

/**
Expand Down Expand Up @@ -418,7 +398,6 @@ export const scopeUseTask: Rule.RuleModule = {
targetFunctionNode.body.type === AST_NODE_TYPES.BlockStatement
? targetFunctionNode.body
: targetFunctionNode.body;

analyzeNodeContent(nodeToAnalyze, targetFunctionNode, callNode);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// Expect error: { "messageId": "unsafeApiUsageInCalledFunction" }

import { component$, useTask$ } from '@qwik.dev/core';

export default component$(() => {
useTask$(() => {
function foo() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// Expect error: { "messageId": "unsafeApiUsage" }

import { component$, useSignal, useTask$ } from '@qwik.dev/core';
// Expect error: { "messageId": "unsafeApiUsage" }
// Expect error: { "messageId": "unsafeApiUsage" }
// Expect error: { "messageId": "unsafeApiUsage" }
import { component$, isBrowser, useSignal, useTask$ } from '@qwik.dev/core';

export default component$(() => {
const s = useSignal(0);
useTask$(({ track }) => {
track(() => {
if (isBrowser) {
process.env;
const m = process;
}
process.env;
const m = process;
return s.value;
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { component$, isServer, useTask$ } from '@qwik.dev/core';

import path from 'path';
export default component$(() => {
useTask$(() => {
function child_process() {}
function foo() {
if (isServer) {
process.env;
const m = process;
const _path = path;
const pathJoin = path.join('foo', 'bar');
}
}
child_process();
const foo2 = () => {
if (isServer) {
process.env;
const m = process;
}
};
foo();
foo2();
});
return <></>;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { component$, useTask$, isBrowser, useSignal } from '@qwik.dev/core';

export default component$(() => {
const state = useSignal(true);
useTask$(({ track }) => {
if (isBrowser) {
track(() => {
if (state.value) {
const values = [
{
path: '1',
},
];
}
});
}
});
return <></>;
});