From 512223fcf200115f97113cf362114b45dce1727f Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 9 Nov 2023 15:54:30 +0100 Subject: [PATCH 01/16] Modify S1874: Merge with `react/no-deprecated` --- .../jsts/src/rules/S1874/rule.diagnostics.ts | 63 +++++++++++++++++++ packages/jsts/src/rules/S1874/rule.ts | 48 +++++--------- 2 files changed, 78 insertions(+), 33 deletions(-) create mode 100644 packages/jsts/src/rules/S1874/rule.diagnostics.ts diff --git a/packages/jsts/src/rules/S1874/rule.diagnostics.ts b/packages/jsts/src/rules/S1874/rule.diagnostics.ts new file mode 100644 index 00000000000..8afaae2952b --- /dev/null +++ b/packages/jsts/src/rules/S1874/rule.diagnostics.ts @@ -0,0 +1,63 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// https://sonarsource.github.io/rspec/#/rspec/S1874/javascript + +import { Rule } from 'eslint'; +import { isRequiredParserServices } from '../helpers'; +import * as ts from 'typescript'; + +export const rule: Rule.RuleModule = { + meta: { + messages: { + deprecation: '{{deprecation}}', + }, + }, + create(context: Rule.RuleContext) { + const services = context.parserServices; + if (!isRequiredParserServices(services)) { + return {}; + } + return { + Program: () => { + const program = services.program; + const checker = program.getTypeChecker(); + const sourceFile = program.getSourceFile(context.filename); + const diagnostics: ts.DiagnosticWithLocation[] = + // @ts-ignore: TypeChecker#getSuggestionDiagnostics is not publicly exposed + checker.getSuggestionDiagnostics(sourceFile); + for (const diagnostic of diagnostics) { + if (diagnostic.reportsDeprecated === true) { + const sourceCode = context.sourceCode; + const start = sourceCode.getLocFromIndex(diagnostic.start); + const end = sourceCode.getLocFromIndex(diagnostic.start + diagnostic.length); + const loc = { start, end }; + context.report({ + loc, + messageId: 'deprecation', + data: { + deprecation: diagnostic.messageText as string, + }, + }); + } + } + }, + }; + }, +}; diff --git a/packages/jsts/src/rules/S1874/rule.ts b/packages/jsts/src/rules/S1874/rule.ts index 8afaae2952b..49e36939c24 100644 --- a/packages/jsts/src/rules/S1874/rule.ts +++ b/packages/jsts/src/rules/S1874/rule.ts @@ -20,44 +20,26 @@ // https://sonarsource.github.io/rspec/#/rspec/S1874/javascript import { Rule } from 'eslint'; -import { isRequiredParserServices } from '../helpers'; -import * as ts from 'typescript'; +import { rule as diagnosticsRule } from './rule.diagnostics'; +import { rules } from 'eslint-plugin-react'; +import { mergeRules } from '../helpers'; + +const reactNoDeprecated = rules['no-deprecated']; export const rule: Rule.RuleModule = { meta: { - messages: { - deprecation: '{{deprecation}}', - }, + messages: { ...reactNoDeprecated.meta!.messages, ...diagnosticsRule.meta!.messages }, }, create(context: Rule.RuleContext) { - const services = context.parserServices; - if (!isRequiredParserServices(services)) { - return {}; + if (context.parserServices?.packageJson?.dependencies?.react) { + if (!context.hasOwnProperty('settings')) { + context.settings = {}; + } + if (!context.settings.hasOwnProperty('react')) { + context.settings.react = {}; + } + context.settings.react.version = context.parserServices.packageJson.dependencies.react; } - return { - Program: () => { - const program = services.program; - const checker = program.getTypeChecker(); - const sourceFile = program.getSourceFile(context.filename); - const diagnostics: ts.DiagnosticWithLocation[] = - // @ts-ignore: TypeChecker#getSuggestionDiagnostics is not publicly exposed - checker.getSuggestionDiagnostics(sourceFile); - for (const diagnostic of diagnostics) { - if (diagnostic.reportsDeprecated === true) { - const sourceCode = context.sourceCode; - const start = sourceCode.getLocFromIndex(diagnostic.start); - const end = sourceCode.getLocFromIndex(diagnostic.start + diagnostic.length); - const loc = { start, end }; - context.report({ - loc, - messageId: 'deprecation', - data: { - deprecation: diagnostic.messageText as string, - }, - }); - } - } - }, - }; + return mergeRules(reactNoDeprecated.create(context), diagnosticsRule.create(context)); }, }; From da921526ee63194339af695766dce3c10cac1d4b Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 9 Nov 2023 17:14:14 +0100 Subject: [PATCH 02/16] proxying context --- packages/jsts/src/rules/S1874/cb.options.json | 5 +++ .../jsts/src/rules/S1874/cb.react.fixture.tsx | 37 +++++++++++++++++++ packages/jsts/src/rules/S1874/rule.ts | 32 +++++++++++----- 3 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 packages/jsts/src/rules/S1874/cb.options.json create mode 100644 packages/jsts/src/rules/S1874/cb.react.fixture.tsx diff --git a/packages/jsts/src/rules/S1874/cb.options.json b/packages/jsts/src/rules/S1874/cb.options.json new file mode 100644 index 00000000000..e91ee686b29 --- /dev/null +++ b/packages/jsts/src/rules/S1874/cb.options.json @@ -0,0 +1,5 @@ +[ + { + "react-version": "15.0" + } +] diff --git a/packages/jsts/src/rules/S1874/cb.react.fixture.tsx b/packages/jsts/src/rules/S1874/cb.react.fixture.tsx new file mode 100644 index 00000000000..0682a1fe61b --- /dev/null +++ b/packages/jsts/src/rules/S1874/cb.react.fixture.tsx @@ -0,0 +1,37 @@ +import React, { PropTypes, Component } from 'react'; +import ReactDOM from 'react-dom'; + +React.render(, root); + +React.unmountComponentAtNode(root); + +React.findDOMNode(this.refs.foo); + +React.renderToString(); + +React.renderToStaticMarkup(); + +React.createClass({ /* Class object */ }); + +const propTypes = { + foo: PropTypes.bar, +}; + +//Any factories under React.DOM +React.DOM.div(); + +class ApiCall extends Component { +// old lifecycles (since React 16.9) + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} +} + +// React 18 deprecations +ReactDOM.render(
, container); + +ReactDOM.hydrate(
, container); + +ReactDOM.unmountComponentAtNode(container); + +ReactDOMServer.renderToNodeStream(element); \ No newline at end of file diff --git a/packages/jsts/src/rules/S1874/rule.ts b/packages/jsts/src/rules/S1874/rule.ts index 49e36939c24..d735b1c3107 100644 --- a/packages/jsts/src/rules/S1874/rule.ts +++ b/packages/jsts/src/rules/S1874/rule.ts @@ -31,15 +31,29 @@ export const rule: Rule.RuleModule = { messages: { ...reactNoDeprecated.meta!.messages, ...diagnosticsRule.meta!.messages }, }, create(context: Rule.RuleContext) { - if (context.parserServices?.packageJson?.dependencies?.react) { - if (!context.hasOwnProperty('settings')) { - context.settings = {}; - } - if (!context.settings.hasOwnProperty('react')) { - context.settings.react = {}; - } - context.settings.react.version = context.parserServices.packageJson.dependencies.react; + function getVersionFromOptions() { + return context.options?.[0]?.['react-version']; + } + function getVersionFromPackageJson() { + return context.parserServices?.packageJson?.dependencies?.react; } - return mergeRules(reactNoDeprecated.create(context), diagnosticsRule.create(context)); + + const reactVersion = getVersionFromOptions() || getVersionFromPackageJson(); + const patchedContext = reactVersion ? createProxy(context, reactVersion) : context; + return mergeRules( + reactNoDeprecated.create(patchedContext), + diagnosticsRule.create(patchedContext), + ); }, }; + +function createProxy(context: Rule.RuleContext, reactVersion: string) { + return new Proxy(context, { + get(target: Rule.RuleContext, key: string | symbol, receiver: any): any { + //https://stackoverflow.com/questions/41299642/how-to-use-javascript-proxy-for-nested-objects + if (key === 'settings' && typeof target[key] === 'object' && target[key] !== null) { + return; + } + }, + }); +} From b2c6fac271ec7626bb9ac6741e4486b3a6f64dbf Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Fri, 10 Nov 2023 16:59:28 +0100 Subject: [PATCH 03/16] wip --- packages/jsts/src/rules/S1874/rule.ts | 43 +++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/jsts/src/rules/S1874/rule.ts b/packages/jsts/src/rules/S1874/rule.ts index d735b1c3107..10f348136bc 100644 --- a/packages/jsts/src/rules/S1874/rule.ts +++ b/packages/jsts/src/rules/S1874/rule.ts @@ -39,7 +39,9 @@ export const rule: Rule.RuleModule = { } const reactVersion = getVersionFromOptions() || getVersionFromPackageJson(); - const patchedContext = reactVersion ? createProxy(context, reactVersion) : context; + const patchedContext = reactVersion + ? createProxy(context, ['settings', 'react', 'version'], reactVersion) + : context; return mergeRules( reactNoDeprecated.create(patchedContext), diagnosticsRule.create(patchedContext), @@ -47,13 +49,42 @@ export const rule: Rule.RuleModule = { }, }; -function createProxy(context: Rule.RuleContext, reactVersion: string) { - return new Proxy(context, { - get(target: Rule.RuleContext, key: string | symbol, receiver: any): any { +function createProxy(target: any, fqn: string[], value: any) { + return new Proxy(target, { + get(target: any, p: string | symbol): any { //https://stackoverflow.com/questions/41299642/how-to-use-javascript-proxy-for-nested-objects - if (key === 'settings' && typeof target[key] === 'object' && target[key] !== null) { - return; + if (p === 'isProxy') { + return true; } + const key = p as keyof typeof target; + + const prop = target[key]; + + if (key === fqn[0]) { + if (fqn.length) { + if (typeof prop == 'undefined') { + return createObject(fqn.slice(1), value); + } + + if (!prop.isProxy && typeof prop === 'object' && target[key] !== null) { + return createProxy(prop, fqn.slice(1), value); + } + } else { + return value; + } + } + return target[key]; }, }); } + +function createObject(fqn: string[], value: any) { + const target: { [key: string]: any } = {}; + const key = fqn[0]; + if (fqn.length > 1) { + target[key] = createObject(fqn.slice(1), value); + } else { + target[fqn[0]] = value; + } + return target; +} From 99f08c9bd1d6f3d2d4c949b876545d5a7564d60c Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Fri, 10 Nov 2023 17:10:30 +0100 Subject: [PATCH 04/16] wip --- packages/jsts/src/rules/S1874/cb.options.json | 2 +- .../jsts/src/rules/S1874/cb.react.fixture.tsx | 31 ++++----- packages/jsts/src/rules/S1874/rule.ts | 46 +------------ packages/shared/src/helpers/index.ts | 1 + packages/shared/src/helpers/proxy.ts | 67 +++++++++++++++++++ 5 files changed, 87 insertions(+), 60 deletions(-) create mode 100644 packages/shared/src/helpers/proxy.ts diff --git a/packages/jsts/src/rules/S1874/cb.options.json b/packages/jsts/src/rules/S1874/cb.options.json index e91ee686b29..e6e97861c5c 100644 --- a/packages/jsts/src/rules/S1874/cb.options.json +++ b/packages/jsts/src/rules/S1874/cb.options.json @@ -1,5 +1,5 @@ [ { - "react-version": "15.0" + "react-version": "19.0" } ] diff --git a/packages/jsts/src/rules/S1874/cb.react.fixture.tsx b/packages/jsts/src/rules/S1874/cb.react.fixture.tsx index 0682a1fe61b..7bfe411c6ec 100644 --- a/packages/jsts/src/rules/S1874/cb.react.fixture.tsx +++ b/packages/jsts/src/rules/S1874/cb.react.fixture.tsx @@ -1,37 +1,38 @@ -import React, { PropTypes, Component } from 'react'; +import React, { PropTypes, Component } from 'react'; // Noncompliant +// ^^^^^^^^^ import ReactDOM from 'react-dom'; -React.render(, root); +React.render(, root); // Noncompliant -React.unmountComponentAtNode(root); +React.unmountComponentAtNode(root); // Noncompliant -React.findDOMNode(this.refs.foo); +React.findDOMNode(this.refs.foo); // Noncompliant -React.renderToString(); +React.renderToString(); // Noncompliant -React.renderToStaticMarkup(); +React.renderToStaticMarkup(); // Noncompliant -React.createClass({ /* Class object */ }); +React.createClass({ /* Class object */ }); // Noncompliant const propTypes = { foo: PropTypes.bar, }; //Any factories under React.DOM -React.DOM.div(); +React.DOM.div(); // Noncompliant class ApiCall extends Component { // old lifecycles (since React 16.9) - componentWillMount() {} - componentWillReceiveProps() {} - componentWillUpdate() {} + componentWillMount() {} // Noncompliant + componentWillReceiveProps() {} // Noncompliant + componentWillUpdate() {} // Noncompliant } // React 18 deprecations -ReactDOM.render(
, container); +ReactDOM.render(
, container); // Noncompliant -ReactDOM.hydrate(
, container); +ReactDOM.hydrate(
, container); // Noncompliant -ReactDOM.unmountComponentAtNode(container); +ReactDOM.unmountComponentAtNode(container); // Noncompliant -ReactDOMServer.renderToNodeStream(element); \ No newline at end of file +ReactDOMServer.renderToNodeStream(element); // Noncompliant diff --git a/packages/jsts/src/rules/S1874/rule.ts b/packages/jsts/src/rules/S1874/rule.ts index 10f348136bc..88530bbdc38 100644 --- a/packages/jsts/src/rules/S1874/rule.ts +++ b/packages/jsts/src/rules/S1874/rule.ts @@ -23,6 +23,7 @@ import { Rule } from 'eslint'; import { rule as diagnosticsRule } from './rule.diagnostics'; import { rules } from 'eslint-plugin-react'; import { mergeRules } from '../helpers'; +import { createProxy } from '@sonar/shared/helpers'; const reactNoDeprecated = rules['no-deprecated']; @@ -42,49 +43,6 @@ export const rule: Rule.RuleModule = { const patchedContext = reactVersion ? createProxy(context, ['settings', 'react', 'version'], reactVersion) : context; - return mergeRules( - reactNoDeprecated.create(patchedContext), - diagnosticsRule.create(patchedContext), - ); + return mergeRules(reactNoDeprecated.create(patchedContext), diagnosticsRule.create(context)); }, }; - -function createProxy(target: any, fqn: string[], value: any) { - return new Proxy(target, { - get(target: any, p: string | symbol): any { - //https://stackoverflow.com/questions/41299642/how-to-use-javascript-proxy-for-nested-objects - if (p === 'isProxy') { - return true; - } - const key = p as keyof typeof target; - - const prop = target[key]; - - if (key === fqn[0]) { - if (fqn.length) { - if (typeof prop == 'undefined') { - return createObject(fqn.slice(1), value); - } - - if (!prop.isProxy && typeof prop === 'object' && target[key] !== null) { - return createProxy(prop, fqn.slice(1), value); - } - } else { - return value; - } - } - return target[key]; - }, - }); -} - -function createObject(fqn: string[], value: any) { - const target: { [key: string]: any } = {}; - const key = fqn[0]; - if (fqn.length > 1) { - target[key] = createObject(fqn.slice(1), value); - } else { - target[fqn[0]] = value; - } - return target; -} diff --git a/packages/shared/src/helpers/index.ts b/packages/shared/src/helpers/index.ts index e5c72b34040..04bb3bc0dc1 100644 --- a/packages/shared/src/helpers/index.ts +++ b/packages/shared/src/helpers/index.ts @@ -22,3 +22,4 @@ export * from './logging'; export * from './files'; export * from './language'; export * from './escape'; +export * from './proxy'; diff --git a/packages/shared/src/helpers/proxy.ts b/packages/shared/src/helpers/proxy.ts new file mode 100644 index 00000000000..271598d022c --- /dev/null +++ b/packages/shared/src/helpers/proxy.ts @@ -0,0 +1,67 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * Proxies an object (useful for frozen objects which cannot be modified). Covers cases like + * when the path to the value does not exist. + * + * @param target Object to be proxied + * @param fqn path to the value that need to be overwritten + * @param value value that will be returned instead of original + */ +export function createProxy(target: any, fqn: string[], value: any) { + return new Proxy(target, { + get(target: any, p: string | symbol): any { + //https://stackoverflow.com/questions/41299642/how-to-use-javascript-proxy-for-nested-objects + if (p === 'isProxy') { + return true; + } + const key = p as keyof typeof target; + + const prop = target[key]; + + if (key === fqn[0]) { + if (fqn.length) { + if (typeof prop == 'undefined') { + return createObject(fqn.slice(1), value); + } + + if (!prop.isProxy && typeof prop === 'object' && target[key] !== null) { + return createProxy(prop, fqn.slice(1), value); + } + } else { + return value; + } + } + return target[key]; + }, + }); +} + +function createObject(fqn: string[], value: any) { + const target: { [key: string]: any } = {}; + const key = fqn[0]; + if (fqn.length > 1) { + target[key] = createObject(fqn.slice(1), value); + } else { + target[fqn[0]] = value; + } + return target; +} From f1d6ed957f4cfd264545264360acf2ca3cd0fe4f Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Fri, 10 Nov 2023 17:18:48 +0100 Subject: [PATCH 05/16] small refactor of proxy creation --- packages/shared/src/helpers/proxy.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/helpers/proxy.ts b/packages/shared/src/helpers/proxy.ts index 271598d022c..58a0b497283 100644 --- a/packages/shared/src/helpers/proxy.ts +++ b/packages/shared/src/helpers/proxy.ts @@ -33,24 +33,26 @@ export function createProxy(target: any, fqn: string[], value: any) { if (p === 'isProxy') { return true; } - const key = p as keyof typeof target; - const prop = target[key]; + const key = p as keyof typeof target; - if (key === fqn[0]) { - if (fqn.length) { - if (typeof prop == 'undefined') { - return createObject(fqn.slice(1), value); - } + // Early return: We do not look for this property + if (key !== fqn[0]) { + return target[key]; + } - if (!prop.isProxy && typeof prop === 'object' && target[key] !== null) { - return createProxy(prop, fqn.slice(1), value); - } - } else { - return value; + const prop = target[key]; + if (fqn.length) { + if (typeof prop !== 'object' || prop === null) { + return createObject(fqn.slice(1), value); + } else if (prop.isProxy) { + return prop; + } else if (typeof prop === 'object') { + return createProxy(prop, fqn.slice(1), value); } + } else { + return value; } - return target[key]; }, }); } From 37d58011b60db3979948ea3aeb23d64748a03c49 Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Fri, 10 Nov 2023 22:51:23 +0100 Subject: [PATCH 06/16] wip --- packages/shared/src/helpers/proxy.ts | 53 ++++++++----- packages/shared/tests/helpers/proxy.test.ts | 86 +++++++++++++++++++++ 2 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 packages/shared/tests/helpers/proxy.test.ts diff --git a/packages/shared/src/helpers/proxy.ts b/packages/shared/src/helpers/proxy.ts index 58a0b497283..53a04b95e58 100644 --- a/packages/shared/src/helpers/proxy.ts +++ b/packages/shared/src/helpers/proxy.ts @@ -18,6 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +export let proxiesCounter = 0; +export let ghostObjectCounter = 0; +export const isProxy = Symbol('isProxy'); +export const isGhostObject = Symbol('isGhostObject'); /** * Proxies an object (useful for frozen objects which cannot be modified). Covers cases like * when the path to the value does not exist. @@ -27,38 +31,40 @@ * @param value value that will be returned instead of original */ export function createProxy(target: any, fqn: string[], value: any) { + proxiesCounter++; + const [propertyName, ...next] = fqn; return new Proxy(target, { - get(target: any, p: string | symbol): any { + cache: {}, + get(target: any, key: string | symbol | number): any { //https://stackoverflow.com/questions/41299642/how-to-use-javascript-proxy-for-nested-objects - if (p === 'isProxy') { + if (key === isProxy) { return true; } - const key = p as keyof typeof target; - - // Early return: We do not look for this property - if (key !== fqn[0]) { - return target[key]; - } - - const prop = target[key]; - if (fqn.length) { - if (typeof prop !== 'object' || prop === null) { - return createObject(fqn.slice(1), value); - } else if (prop.isProxy) { - return prop; - } else if (typeof prop === 'object') { - return createProxy(prop, fqn.slice(1), value); + const actualValue = target[key]; + if (propertyName && key === propertyName) { + if (!this.cache[key]) { + if (typeof actualValue !== 'object' || actualValue === null) { + if (next.length) { + this.cache[key] = createObject(next, value); + } else { + this.cache[key] = value; + } + } else { + this.cache[key] = createProxy(actualValue, next, value); + } } - } else { - return value; + return this.cache[key]; } + return target[key]; }, - }); + } as ProxyHandler & { cache: { [key: string | number | symbol]: any } }); } function createObject(fqn: string[], value: any) { - const target: { [key: string]: any } = {}; + ghostObjectCounter++; + const target: { [key: string | number | symbol]: any } = {}; + target[isGhostObject] = true; const key = fqn[0]; if (fqn.length > 1) { target[key] = createObject(fqn.slice(1), value); @@ -67,3 +73,8 @@ function createObject(fqn: string[], value: any) { } return target; } + +export function resetCounters() { + proxiesCounter = 0; + ghostObjectCounter = 0; +} diff --git a/packages/shared/tests/helpers/proxy.test.ts b/packages/shared/tests/helpers/proxy.test.ts new file mode 100644 index 00000000000..31c057a2af7 --- /dev/null +++ b/packages/shared/tests/helpers/proxy.test.ts @@ -0,0 +1,86 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { + resetCounters, + createProxy, + proxiesCounter, + isProxy, + isGhostObject, + ghostObjectCounter, +} from '../../src/helpers'; + +describe('proxy', () => { + beforeEach(() => { + resetCounters(); + }); + it('should proxy an object', () => { + expect(proxiesCounter).toEqual(0); + const obj = { a: 1 } as any; + freezeDeeply(obj); + expect(() => (obj.b = 0)).toThrow(TypeError); + const objProxy = createProxy(obj, ['b'], 0); + expect(objProxy.b).toEqual(0); + expect(objProxy[isProxy]).toEqual(true); + expect(proxiesCounter).toEqual(1); + }); + it('should proxy non-existing nested object', () => { + expect(proxiesCounter).toEqual(0); + const obj = { a: 1 } as any; + freezeDeeply(obj); + expect(() => (obj.b = { c: { d: 1 } })).toThrow(TypeError); + const objProxy = createProxy(obj, ['b', 'c', 'd'], 1); + expect(objProxy.b.c.d).toEqual(1); + + expect(objProxy[isProxy]).toEqual(true); + expect(objProxy.b[isGhostObject]).toEqual(true); + expect(objProxy.b.c[isGhostObject]).toEqual(true); + expect(proxiesCounter).toEqual(1); + expect(ghostObjectCounter).toEqual(2); + }); + it('should proxy existing nested object', () => { + expect(proxiesCounter).toEqual(0); + const obj = { a: 1, b: { c: {} } } as any; + freezeDeeply(obj); + expect(() => (obj.b = { c: { d: 1 } })).toThrow(TypeError); + const objProxy = createProxy(obj, ['b', 'c', 'd'], 1); + expect(objProxy.b.c.d).toEqual(1); + + expect(objProxy[isProxy]).toEqual(true); + expect(objProxy.b[isProxy]).toEqual(true); + expect(objProxy.b.c[isProxy]).toEqual(true); + expect(proxiesCounter).toEqual(2); + expect(ghostObjectCounter).toEqual(1); + }); +}); + +function freezeDeeply(x: any) { + if (typeof x === 'object' && x !== null) { + if (Array.isArray(x)) { + x.forEach(freezeDeeply); + } else { + for (const key in x) { + if (x.hasOwnProperty(key)) { + freezeDeeply(x[key]); + } + } + } + Object.freeze(x); + } +} From fe8e775d4ad18492a1a888f9c8212059bab9358f Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Sat, 11 Nov 2023 00:22:10 +0100 Subject: [PATCH 07/16] add tests --- packages/shared/tests/helpers/proxy.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/shared/tests/helpers/proxy.test.ts b/packages/shared/tests/helpers/proxy.test.ts index 31c057a2af7..20c7642c815 100644 --- a/packages/shared/tests/helpers/proxy.test.ts +++ b/packages/shared/tests/helpers/proxy.test.ts @@ -56,7 +56,9 @@ describe('proxy', () => { }); it('should proxy existing nested object', () => { expect(proxiesCounter).toEqual(0); - const obj = { a: 1, b: { c: {} } } as any; + const objProto = { b: { c: {} } } as any; + const obj = Object.create(objProto); + obj.a = 1; freezeDeeply(obj); expect(() => (obj.b = { c: { d: 1 } })).toThrow(TypeError); const objProxy = createProxy(obj, ['b', 'c', 'd'], 1); @@ -65,8 +67,8 @@ describe('proxy', () => { expect(objProxy[isProxy]).toEqual(true); expect(objProxy.b[isProxy]).toEqual(true); expect(objProxy.b.c[isProxy]).toEqual(true); - expect(proxiesCounter).toEqual(2); - expect(ghostObjectCounter).toEqual(1); + expect(proxiesCounter).toEqual(3); + expect(ghostObjectCounter).toEqual(0); }); }); From 64442300c410baa7ccd9745c3c5248f8e9865b84 Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Mon, 13 Nov 2023 11:55:08 +0100 Subject: [PATCH 08/16] remove proxy implementation add ruling results --- .../jsts/postgraphql/javascript-S1874.json | 5 + .../jsts/reddit-mobile/javascript-S1874.json | 9 + .../expected/jsts/redux/javascript-S1874.json | 122 +++++ .../jsts/sonar-web/javascript-S1874.json | 513 ++++++++++++++++++ packages/jsts/src/rules/S1874/rule.ts | 14 +- packages/shared/src/helpers/index.ts | 1 - packages/shared/src/helpers/proxy.ts | 80 --- packages/shared/tests/helpers/proxy.test.ts | 88 --- 8 files changed, 660 insertions(+), 172 deletions(-) create mode 100644 its/ruling/src/test/expected/jsts/postgraphql/javascript-S1874.json create mode 100644 its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S1874.json create mode 100644 its/ruling/src/test/expected/jsts/redux/javascript-S1874.json delete mode 100644 packages/shared/src/helpers/proxy.ts delete mode 100644 packages/shared/tests/helpers/proxy.test.ts diff --git a/its/ruling/src/test/expected/jsts/postgraphql/javascript-S1874.json b/its/ruling/src/test/expected/jsts/postgraphql/javascript-S1874.json new file mode 100644 index 00000000000..58bb03767ea --- /dev/null +++ b/its/ruling/src/test/expected/jsts/postgraphql/javascript-S1874.json @@ -0,0 +1,5 @@ +{ +"postgraphql:src/postgraphql/graphiql/src/index.js": [ +7 +] +} diff --git a/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S1874.json b/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S1874.json new file mode 100644 index 00000000000..2af1eb1e6df --- /dev/null +++ b/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S1874.json @@ -0,0 +1,9 @@ +{ +"reddit-mobile:assets/js/client.es6.js": [ +212, +297 +], +"reddit-mobile:src/propTypes.es6.js": [ +3 +] +} diff --git a/its/ruling/src/test/expected/jsts/redux/javascript-S1874.json b/its/ruling/src/test/expected/jsts/redux/javascript-S1874.json new file mode 100644 index 00000000000..d3e53781ef3 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/redux/javascript-S1874.json @@ -0,0 +1,122 @@ +{ +"redux:examples/async/components/Picker.js": [ +1 +], +"redux:examples/async/components/Posts.js": [ +1 +], +"redux:examples/async/containers/App.js": [ +1, +19 +], +"redux:examples/async/index.js": [ +3 +], +"redux:examples/counter/components/Counter.js": [ +1 +], +"redux:examples/counter/index.js": [ +2 +], +"redux:examples/real-world/components/Explore.js": [ +1, +12 +], +"redux:examples/real-world/components/List.js": [ +1 +], +"redux:examples/real-world/components/Repo.js": [ +1 +], +"redux:examples/real-world/components/User.js": [ +1 +], +"redux:examples/real-world/containers/App.js": [ +1 +], +"redux:examples/real-world/containers/RepoPage.js": [ +1, +21, +25 +], +"redux:examples/real-world/containers/Root.dev.js": [ +1 +], +"redux:examples/real-world/containers/Root.prod.js": [ +1 +], +"redux:examples/real-world/containers/UserPage.js": [ +1, +22, +26 +], +"redux:examples/real-world/index.js": [ +3 +], +"redux:examples/shopping-cart/components/Cart.js": [ +1 +], +"redux:examples/shopping-cart/components/Product.js": [ +1 +], +"redux:examples/shopping-cart/components/ProductItem.js": [ +1 +], +"redux:examples/shopping-cart/components/ProductsList.js": [ +1 +], +"redux:examples/shopping-cart/containers/CartContainer.js": [ +1 +], +"redux:examples/shopping-cart/containers/ProductsContainer.js": [ +1 +], +"redux:examples/shopping-cart/index.js": [ +2 +], +"redux:examples/todomvc/components/Footer.js": [ +1 +], +"redux:examples/todomvc/components/Header.js": [ +1 +], +"redux:examples/todomvc/components/MainSection.js": [ +1 +], +"redux:examples/todomvc/components/TodoItem.js": [ +1 +], +"redux:examples/todomvc/components/TodoTextInput.js": [ +1 +], +"redux:examples/todomvc/containers/App.js": [ +1 +], +"redux:examples/todomvc/index.js": [ +3 +], +"redux:examples/todos-with-undo/components/AddTodo.js": [ +1 +], +"redux:examples/todos-with-undo/components/Footer.js": [ +1 +], +"redux:examples/todos-with-undo/components/Todo.js": [ +1 +], +"redux:examples/todos-with-undo/components/TodoList.js": [ +1 +], +"redux:examples/todos-with-undo/containers/App.js": [ +1 +], +"redux:examples/todos-with-undo/index.js": [ +2 +], +"redux:examples/universal/client/index.js": [ +3 +], +"redux:examples/universal/common/components/Counter.js": [ +1 +] +} diff --git a/its/ruling/src/test/expected/jsts/sonar-web/javascript-S1874.json b/its/ruling/src/test/expected/jsts/sonar-web/javascript-S1874.json index 057cd8a7a68..94c1fa605cd 100644 --- a/its/ruling/src/test/expected/jsts/sonar-web/javascript-S1874.json +++ b/its/ruling/src/test/expected/jsts/sonar-web/javascript-S1874.json @@ -1,7 +1,482 @@ { +"sonar-web:src/main/js/apps/background-tasks/app.js": [ +7 +], +"sonar-web:src/main/js/apps/background-tasks/header.js": [ +3 +], +"sonar-web:src/main/js/apps/background-tasks/main.js": [ +14 +], +"sonar-web:src/main/js/apps/background-tasks/search.js": [ +8 +], +"sonar-web:src/main/js/apps/background-tasks/stats.js": [ +4 +], +"sonar-web:src/main/js/apps/background-tasks/tasks.js": [ +9, +11, +11 +], +"sonar-web:src/main/js/apps/global-permissions/app.js": [ +8 +], +"sonar-web:src/main/js/apps/global-permissions/main.js": [ +5 +], +"sonar-web:src/main/js/apps/global-permissions/permission-groups.js": [ +5 +], +"sonar-web:src/main/js/apps/global-permissions/permission-users-groups-mixin.js": [ +5, +6, +7, +8, +9 +], +"sonar-web:src/main/js/apps/global-permissions/permission-users.js": [ +5 +], +"sonar-web:src/main/js/apps/global-permissions/permission.js": [ +9, +11 +], +"sonar-web:src/main/js/apps/global-permissions/permissions-list.js": [ +7, +9, +9 +], +"sonar-web:src/main/js/apps/overview/app.js": [ +22, +24 +], +"sonar-web:src/main/js/apps/overview/components/complexity-distribution.js": [ +10, +12 +], +"sonar-web:src/main/js/apps/overview/components/coverage-measure.js": [ +9 +], +"sonar-web:src/main/js/apps/overview/components/coverage-measures-list.js": [ +27 +], +"sonar-web:src/main/js/apps/overview/components/detailed-measure.js": [ +8 +], +"sonar-web:src/main/js/apps/overview/components/domain-bubble-chart.js": [ +122, +123, +124, +124 +], +"sonar-web:src/main/js/apps/overview/components/domain-timeline.js": [ +20, +22, +22, +23, +23, +24 +], +"sonar-web:src/main/js/apps/overview/components/domain-treemap.js": [ +99, +100, +101 +], +"sonar-web:src/main/js/apps/overview/components/issue-measure.js": [ +11, +60, +113, +166, +211 +], +"sonar-web:src/main/js/apps/overview/components/issues-tags.js": [ +8 +], +"sonar-web:src/main/js/apps/overview/components/language-distribution.js": [ +9, +11, +12 +], +"sonar-web:src/main/js/apps/overview/components/legend.js": [ +6 +], +"sonar-web:src/main/js/apps/overview/components/timeline-chart.js": [ +10, +14, +14, +15, +15, +16, +17 +], +"sonar-web:src/main/js/apps/overview/domains/coverage-domain.js": [ +19 +], +"sonar-web:src/main/js/apps/overview/domains/debt-domain.js": [ +28 +], +"sonar-web:src/main/js/apps/overview/domains/duplications-domain.js": [ +16 +], +"sonar-web:src/main/js/apps/overview/domains/size-domain.js": [ +15 +], +"sonar-web:src/main/js/apps/overview/gate/gate-condition.js": [ +8, +19 +], +"sonar-web:src/main/js/apps/overview/gate/gate-conditions.js": [ +4, +6, +7 +], +"sonar-web:src/main/js/apps/overview/gate/gate-empty.js": [ +3 +], +"sonar-web:src/main/js/apps/overview/gate/gate.js": [ +7 +], +"sonar-web:src/main/js/apps/overview/main/components.js": [ +9, +16, +38, +51, +61, +63, +74, +80, +87, +94, +96, +97 +], +"sonar-web:src/main/js/apps/overview/main/coverage.js": [ +10, +14, +15, +16 +], +"sonar-web:src/main/js/apps/overview/main/duplications.js": [ +10, +14, +15 +], +"sonar-web:src/main/js/apps/overview/main/issues.js": [ +14, +18, +19 +], +"sonar-web:src/main/js/apps/overview/main/main.js": [ +44, +46 +], +"sonar-web:src/main/js/apps/overview/main/size.js": [ +11, +15, +16 +], +"sonar-web:src/main/js/apps/overview/main/timeline.js": [ +44, +44, +45, +46 +], +"sonar-web:src/main/js/apps/overview/meta.js": [ +6 +], +"sonar-web:src/main/js/apps/overview/overview.js": [ +15, +92 +], +"sonar-web:src/main/js/apps/permission-templates/app.js": [ +8 +], +"sonar-web:src/main/js/apps/permission-templates/header.js": [ +4 +], +"sonar-web:src/main/js/apps/permission-templates/main.js": [ +9, +11 +], +"sonar-web:src/main/js/apps/permission-templates/permission-template-defaults.js": [ +5, +7, +8 +], +"sonar-web:src/main/js/apps/permission-templates/permission-template-set-defaults.js": [ +6, +8, +9, +10 +], +"sonar-web:src/main/js/apps/permission-templates/permission-template.js": [ +11, +13, +14, +15 +], +"sonar-web:src/main/js/apps/permission-templates/permission-templates.js": [ +8, +10, +10, +11, +11, +12, +13 +], +"sonar-web:src/main/js/apps/permission-templates/permissions-header.js": [ +3, +5, +5 +], +"sonar-web:src/main/js/apps/project-permissions/app.js": [ +14 +], +"sonar-web:src/main/js/apps/project-permissions/main.js": [ +11, +13, +13 +], +"sonar-web:src/main/js/apps/project-permissions/permissions-footer.js": [ +5, +7, +8, +9 +], +"sonar-web:src/main/js/apps/project-permissions/permissions-header.js": [ +3, +5, +5 +], +"sonar-web:src/main/js/apps/project-permissions/permissions.js": [ +8, +10, +10, +11, +11, +12, +12, +13 +], +"sonar-web:src/main/js/apps/project-permissions/project.js": [ +8, +10, +11, +11, +12 +], +"sonar-web:src/main/js/apps/project-permissions/search.js": [ +4, +6 +], +"sonar-web:src/main/js/apps/projects/app.js": [ +12 +], +"sonar-web:src/main/js/apps/projects/header.js": [ +4, +6 +], +"sonar-web:src/main/js/apps/projects/main.js": [ +10, +12, +13 +], +"sonar-web:src/main/js/apps/projects/projects.js": [ +7, +9, +10, +11 +], +"sonar-web:src/main/js/apps/projects/search.js": [ +8, +10 +], +"sonar-web:src/main/js/apps/system/app.js": [ +7 +], +"sonar-web:src/main/js/apps/system/item-boolean.js": [ +3 +], +"sonar-web:src/main/js/apps/system/item-log-level.js": [ +6 +], +"sonar-web:src/main/js/apps/system/item-object.js": [ +4 +], +"sonar-web:src/main/js/apps/system/item-value.js": [ +6 +], +"sonar-web:src/main/js/apps/system/main.js": [ +9 +], +"sonar-web:src/main/js/apps/system/section.js": [ +4 +], +"sonar-web:src/main/js/components/charts/bar-chart.js": [ +7, +11, +11, +12, +12, +13, +13, +14, +15, +15, +16 +], +"sonar-web:src/main/js/components/charts/bubble-chart.js": [ +9, +11, +12, +13, +14, +15, +39, +43, +43, +44, +44, +45, +46, +47, +48, +49, +50, +50, +51, +52 +], +"sonar-web:src/main/js/components/charts/donut-chart.js": [ +8, +18, +22, +22 +], +"sonar-web:src/main/js/components/charts/histogram.js": [ +7, +11, +11, +12, +12, +13, +13, +14, +15, +16, +16, +17 +], +"sonar-web:src/main/js/components/charts/line-chart.js": [ +8, +12, +12, +13, +13, +14, +14, +15, +15, +16, +16, +17, +18, +19, +20, +21 +], +"sonar-web:src/main/js/components/charts/treemap.js": [ +30, +32, +33, +34, +35, +36, +37, +38, +67, +71, +71, +72 +], +"sonar-web:src/main/js/components/charts/word-cloud.js": [ +7, +9, +10, +11, +12, +28, +32, +32, +33, +33 +], "sonar-web:src/main/js/components/router/router.js": [ 16 ], +"sonar-web:src/main/js/components/shared/avatar.js": [ +4, +6, +7 +], +"sonar-web:src/main/js/components/shared/checkbox.js": [ +3, +5, +6, +7 +], +"sonar-web:src/main/js/components/shared/drilldown-link.js": [ +28 +], +"sonar-web:src/main/js/components/shared/favorite.js": [ +4, +6, +7 +], +"sonar-web:src/main/js/components/shared/issues-link.js": [ +6 +], +"sonar-web:src/main/js/components/shared/list-footer.js": [ +5, +7, +8, +9 +], +"sonar-web:src/main/js/components/shared/pending-icon.js": [ +3 +], +"sonar-web:src/main/js/components/shared/qualifier-icon.js": [ +3 +], +"sonar-web:src/main/js/components/shared/quality-gate-link.js": [ +4 +], +"sonar-web:src/main/js/components/shared/quality-profile-link.js": [ +4 +], +"sonar-web:src/main/js/components/shared/radio-toggle.js": [ +3, +5, +6, +7, +8 +], +"sonar-web:src/main/js/components/shared/rating.js": [ +6 +], +"sonar-web:src/main/js/components/shared/severity-helper.js": [ +4 +], +"sonar-web:src/main/js/components/shared/severity-icon.js": [ +3 +], +"sonar-web:src/main/js/components/shared/status-helper.js": [ +4 +], +"sonar-web:src/main/js/components/shared/status-icon.js": [ +3 +], "sonar-web:src/main/js/components/source-viewer/helpers/code-with-issue-locations-helper.js": [ 27, 27 @@ -11,5 +486,43 @@ ], "sonar-web:src/main/js/libs/translate.js": [ 76 +], +"sonar-web:src/main/js/main/nav/app.js": [ +40, +50, +61 +], +"sonar-web:src/main/js/main/nav/component/component-nav-breadcrumbs.js": [ +4 +], +"sonar-web:src/main/js/main/nav/component/component-nav-favorite.js": [ +4 +], +"sonar-web:src/main/js/main/nav/component/component-nav-menu.js": [ +12 +], +"sonar-web:src/main/js/main/nav/component/component-nav-meta.js": [ +5 +], +"sonar-web:src/main/js/main/nav/component/component-nav.js": [ +13 +], +"sonar-web:src/main/js/main/nav/global/global-nav-branding.js": [ +3 +], +"sonar-web:src/main/js/main/nav/global/global-nav-menu.js": [ +5 +], +"sonar-web:src/main/js/main/nav/global/global-nav-search.js": [ +15 +], +"sonar-web:src/main/js/main/nav/global/global-nav-user.js": [ +5 +], +"sonar-web:src/main/js/main/nav/global/global-nav.js": [ +8 +], +"sonar-web:src/main/js/main/nav/settings/settings-nav.js": [ +4 ] } diff --git a/packages/jsts/src/rules/S1874/rule.ts b/packages/jsts/src/rules/S1874/rule.ts index 88530bbdc38..80492100d4d 100644 --- a/packages/jsts/src/rules/S1874/rule.ts +++ b/packages/jsts/src/rules/S1874/rule.ts @@ -23,7 +23,6 @@ import { Rule } from 'eslint'; import { rule as diagnosticsRule } from './rule.diagnostics'; import { rules } from 'eslint-plugin-react'; import { mergeRules } from '../helpers'; -import { createProxy } from '@sonar/shared/helpers'; const reactNoDeprecated = rules['no-deprecated']; @@ -36,12 +35,21 @@ export const rule: Rule.RuleModule = { return context.options?.[0]?.['react-version']; } function getVersionFromPackageJson() { - return context.parserServices?.packageJson?.dependencies?.react; + return ( + context.parserServices?.packageJson?.dependencies?.react || + context.parserServices?.packageJson?.devDependencies?.react + ); } const reactVersion = getVersionFromOptions() || getVersionFromPackageJson(); + const patchedContext = reactVersion - ? createProxy(context, ['settings', 'react', 'version'], reactVersion) + ? Object.create(context, { + settings: { + value: { react: { version: reactVersion } }, + writable: false, + }, + }) : context; return mergeRules(reactNoDeprecated.create(patchedContext), diagnosticsRule.create(context)); }, diff --git a/packages/shared/src/helpers/index.ts b/packages/shared/src/helpers/index.ts index 04bb3bc0dc1..e5c72b34040 100644 --- a/packages/shared/src/helpers/index.ts +++ b/packages/shared/src/helpers/index.ts @@ -22,4 +22,3 @@ export * from './logging'; export * from './files'; export * from './language'; export * from './escape'; -export * from './proxy'; diff --git a/packages/shared/src/helpers/proxy.ts b/packages/shared/src/helpers/proxy.ts deleted file mode 100644 index 53a04b95e58..00000000000 --- a/packages/shared/src/helpers/proxy.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarQube JavaScript Plugin - * Copyright (C) 2011-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -export let proxiesCounter = 0; -export let ghostObjectCounter = 0; -export const isProxy = Symbol('isProxy'); -export const isGhostObject = Symbol('isGhostObject'); -/** - * Proxies an object (useful for frozen objects which cannot be modified). Covers cases like - * when the path to the value does not exist. - * - * @param target Object to be proxied - * @param fqn path to the value that need to be overwritten - * @param value value that will be returned instead of original - */ -export function createProxy(target: any, fqn: string[], value: any) { - proxiesCounter++; - const [propertyName, ...next] = fqn; - return new Proxy(target, { - cache: {}, - get(target: any, key: string | symbol | number): any { - //https://stackoverflow.com/questions/41299642/how-to-use-javascript-proxy-for-nested-objects - if (key === isProxy) { - return true; - } - - const actualValue = target[key]; - if (propertyName && key === propertyName) { - if (!this.cache[key]) { - if (typeof actualValue !== 'object' || actualValue === null) { - if (next.length) { - this.cache[key] = createObject(next, value); - } else { - this.cache[key] = value; - } - } else { - this.cache[key] = createProxy(actualValue, next, value); - } - } - return this.cache[key]; - } - return target[key]; - }, - } as ProxyHandler & { cache: { [key: string | number | symbol]: any } }); -} - -function createObject(fqn: string[], value: any) { - ghostObjectCounter++; - const target: { [key: string | number | symbol]: any } = {}; - target[isGhostObject] = true; - const key = fqn[0]; - if (fqn.length > 1) { - target[key] = createObject(fqn.slice(1), value); - } else { - target[fqn[0]] = value; - } - return target; -} - -export function resetCounters() { - proxiesCounter = 0; - ghostObjectCounter = 0; -} diff --git a/packages/shared/tests/helpers/proxy.test.ts b/packages/shared/tests/helpers/proxy.test.ts deleted file mode 100644 index 20c7642c815..00000000000 --- a/packages/shared/tests/helpers/proxy.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SonarQube JavaScript Plugin - * Copyright (C) 2011-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { - resetCounters, - createProxy, - proxiesCounter, - isProxy, - isGhostObject, - ghostObjectCounter, -} from '../../src/helpers'; - -describe('proxy', () => { - beforeEach(() => { - resetCounters(); - }); - it('should proxy an object', () => { - expect(proxiesCounter).toEqual(0); - const obj = { a: 1 } as any; - freezeDeeply(obj); - expect(() => (obj.b = 0)).toThrow(TypeError); - const objProxy = createProxy(obj, ['b'], 0); - expect(objProxy.b).toEqual(0); - expect(objProxy[isProxy]).toEqual(true); - expect(proxiesCounter).toEqual(1); - }); - it('should proxy non-existing nested object', () => { - expect(proxiesCounter).toEqual(0); - const obj = { a: 1 } as any; - freezeDeeply(obj); - expect(() => (obj.b = { c: { d: 1 } })).toThrow(TypeError); - const objProxy = createProxy(obj, ['b', 'c', 'd'], 1); - expect(objProxy.b.c.d).toEqual(1); - - expect(objProxy[isProxy]).toEqual(true); - expect(objProxy.b[isGhostObject]).toEqual(true); - expect(objProxy.b.c[isGhostObject]).toEqual(true); - expect(proxiesCounter).toEqual(1); - expect(ghostObjectCounter).toEqual(2); - }); - it('should proxy existing nested object', () => { - expect(proxiesCounter).toEqual(0); - const objProto = { b: { c: {} } } as any; - const obj = Object.create(objProto); - obj.a = 1; - freezeDeeply(obj); - expect(() => (obj.b = { c: { d: 1 } })).toThrow(TypeError); - const objProxy = createProxy(obj, ['b', 'c', 'd'], 1); - expect(objProxy.b.c.d).toEqual(1); - - expect(objProxy[isProxy]).toEqual(true); - expect(objProxy.b[isProxy]).toEqual(true); - expect(objProxy.b.c[isProxy]).toEqual(true); - expect(proxiesCounter).toEqual(3); - expect(ghostObjectCounter).toEqual(0); - }); -}); - -function freezeDeeply(x: any) { - if (typeof x === 'object' && x !== null) { - if (Array.isArray(x)) { - x.forEach(freezeDeeply); - } else { - for (const key in x) { - if (x.hasOwnProperty(key)) { - freezeDeeply(x[key]); - } - } - } - Object.freeze(x); - } -} From c5c57d747d6631dea89143e6536224d074538288 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 14 Nov 2023 11:02:41 +0100 Subject: [PATCH 09/16] use new implementation --- packages/jsts/src/rules/S1874/rule.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/jsts/src/rules/S1874/rule.ts b/packages/jsts/src/rules/S1874/rule.ts index 80492100d4d..64d4b02f759 100644 --- a/packages/jsts/src/rules/S1874/rule.ts +++ b/packages/jsts/src/rules/S1874/rule.ts @@ -23,6 +23,7 @@ import { Rule } from 'eslint'; import { rule as diagnosticsRule } from './rule.diagnostics'; import { rules } from 'eslint-plugin-react'; import { mergeRules } from '../helpers'; +import { getNearestPackageJsons } from '@sonar/jsts'; const reactNoDeprecated = rules['no-deprecated']; @@ -35,10 +36,15 @@ export const rule: Rule.RuleModule = { return context.options?.[0]?.['react-version']; } function getVersionFromPackageJson() { - return ( - context.parserServices?.packageJson?.dependencies?.react || - context.parserServices?.packageJson?.devDependencies?.react - ); + for (const { contents: packageJson } of getNearestPackageJsons(context.filename)) { + if (packageJson.dependencies?.react) { + return packageJson.dependencies.react; + } + if (packageJson.devDependencies?.react) { + return packageJson.devDependencies.react; + } + } + return null; } const reactVersion = getVersionFromOptions() || getVersionFromPackageJson(); From 60ab2676c9f26d62ce3307abc7d4fbdccfb8e3eb Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 14 Nov 2023 11:13:21 +0100 Subject: [PATCH 10/16] Load package.json files synchronously --- packages/bridge/src/worker.js | 2 +- .../src/dependencies/package-json/index.ts | 4 ++-- .../package-json/project-package-json.ts | 14 +++++++------- packages/jsts/tests/analysis/analyzer.test.ts | 2 +- packages/jsts/tests/dependencies/index.test.ts | 18 +++++++++--------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/bridge/src/worker.js b/packages/bridge/src/worker.js index 69bd44b4651..56beeb1f491 100644 --- a/packages/bridge/src/worker.js +++ b/packages/bridge/src/worker.js @@ -139,7 +139,7 @@ if (parentPort) { case 'on-init-linter': { const { rules, environments, globals, linterId, baseDir, exclusions } = data; initializeLinter(rules, environments, globals, linterId); - await searchPackageJsonFiles(baseDir, exclusions); + searchPackageJsonFiles(baseDir, exclusions); parentThread.postMessage({ type: 'success', result: 'OK!' }); break; } diff --git a/packages/jsts/src/dependencies/package-json/index.ts b/packages/jsts/src/dependencies/package-json/index.ts index 0e1a7d233d9..fc8dcf2fc1d 100644 --- a/packages/jsts/src/dependencies/package-json/index.ts +++ b/packages/jsts/src/dependencies/package-json/index.ts @@ -22,8 +22,8 @@ import { PackageJsons } from './project-package-json'; const PackageJsonsByBaseDir = new PackageJsons(); -async function searchPackageJsonFiles(baseDir: string, exclusions: string[]) { - await PackageJsonsByBaseDir.searchPackageJsonFiles(baseDir, exclusions); +function searchPackageJsonFiles(baseDir: string, exclusions: string[]) { + PackageJsonsByBaseDir.searchPackageJsonFiles(baseDir, exclusions); } function getNearestPackageJsons(file: string) { diff --git a/packages/jsts/src/dependencies/package-json/project-package-json.ts b/packages/jsts/src/dependencies/package-json/project-package-json.ts index fbcafcdc5a7..cdfd6ea1ccc 100644 --- a/packages/jsts/src/dependencies/package-json/project-package-json.ts +++ b/packages/jsts/src/dependencies/package-json/project-package-json.ts @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import fs from 'fs/promises'; +import fs from 'fs'; import path from 'path'; -import { toUnixPath, debug, error, readFile } from '@sonar/shared/helpers'; +import { toUnixPath, debug, error, readFileSync } from '@sonar/shared/helpers'; import { PackageJson as PJ } from 'type-fest'; import { Minimatch } from 'minimatch'; @@ -48,25 +48,25 @@ export class PackageJsons { const patterns = exclusions .concat(IGNORED_PATTERNS) .map(exclusion => new Minimatch(exclusion)); - await this.walkDirectory(path.posix.normalize(toUnixPath(dir)), patterns); + this.walkDirectory(path.posix.normalize(toUnixPath(dir)), patterns); } catch (e) { error(`Error while searching for package.json files: ${e}`); } } - async walkDirectory(dir: string, ignoredPatterns: Minimatch[]) { - const files = await fs.readdir(dir, { withFileTypes: true }); + walkDirectory(dir: string, ignoredPatterns: Minimatch[]) { + const files = fs.readdirSync(dir, { withFileTypes: true }); for (const file of files) { const filename = path.posix.join(dir, file.name); if (ignoredPatterns.some(pattern => pattern.match(filename))) { continue; // is ignored pattern } if (file.isDirectory()) { - await this.walkDirectory(filename, ignoredPatterns); + this.walkDirectory(filename, ignoredPatterns); } else if (file.name.toLowerCase() === PACKAGE_JSON && !file.isDirectory()) { try { debug(`Found package.json: ${filename}`); - const contents = JSON.parse(await readFile(filename)); + const contents = JSON.parse(readFileSync(filename)); this.db.set(dir, { filename, contents }); } catch (e) { debug(`Error reading file ${filename}: ${e}`); diff --git a/packages/jsts/tests/analysis/analyzer.test.ts b/packages/jsts/tests/analysis/analyzer.test.ts index 4c018a6a6e7..75705973ad0 100644 --- a/packages/jsts/tests/analysis/analyzer.test.ts +++ b/packages/jsts/tests/analysis/analyzer.test.ts @@ -899,7 +899,7 @@ describe('analyzeJSTS', () => { it('package.json should be available in rule context', async () => { const baseDir = path.join(__dirname, 'fixtures', 'package-json'); - await searchPackageJsonFiles(baseDir, []); + searchPackageJsonFiles(baseDir, []); const linter = new Linter(); linter.defineRule('custom-rule-file', { diff --git a/packages/jsts/tests/dependencies/index.test.ts b/packages/jsts/tests/dependencies/index.test.ts index c210e78b22c..e33cb725313 100644 --- a/packages/jsts/tests/dependencies/index.test.ts +++ b/packages/jsts/tests/dependencies/index.test.ts @@ -32,9 +32,9 @@ describe('initialize package.json files', () => { getAllPackageJsons().clear(); }); - it('should find all package.json files', async () => { + it('should find all package.json files', () => { const baseDir = path.posix.join(toUnixPath(__dirname), 'fixtures'); - await searchPackageJsonFiles(baseDir, []); + searchPackageJsonFiles(baseDir, []); expect(getAllPackageJsons().size).toEqual(7); const basePJList = getNearestPackageJsons(path.posix.join(baseDir, 'index.js')); @@ -105,10 +105,10 @@ describe('initialize package.json files', () => { expect(fakeFilePJList[0].filename).toEqual(moduleBsubmoduleBPJ); }); - it('should ignore package.json files from ignored patterns', async () => { + it('should ignore package.json files from ignored patterns', () => { const baseDir = path.posix.join(toUnixPath(__dirname), 'fixtures'); - await searchPackageJsonFiles(baseDir, ['**/moduleA/**']); + searchPackageJsonFiles(baseDir, ['**/moduleA/**']); expect(getAllPackageJsons().size).toEqual(4); const packageJsons = [ ['package.json'], @@ -126,7 +126,7 @@ describe('initialize package.json files', () => { ); getAllPackageJsons().clear(); - await searchPackageJsonFiles(baseDir, ['**/module*/**']); + searchPackageJsonFiles(baseDir, ['**/module*/**']); expect(getAllPackageJsons().size).toEqual(1); expect(getAllPackageJsons()).toEqual( new Map([ @@ -138,7 +138,7 @@ describe('initialize package.json files', () => { ); }); - it('should return empty array when no package.json are in the DB or none exist in the file tree', async () => { + it('should return empty array when no package.json are in the DB or none exist in the file tree', () => { const baseDir = path.posix.join(toUnixPath(__dirname), 'fixtures'); expect(getAllPackageJsons().size).toEqual(0); @@ -146,14 +146,14 @@ describe('initialize package.json files', () => { getNearestPackageJsons(path.posix.join(baseDir, '..', 'another-module', 'index.js')), ).toHaveLength(0); - await searchPackageJsonFiles(baseDir, ['']); + searchPackageJsonFiles(baseDir, ['']); expect(getAllPackageJsons().size).toEqual(7); expect( getNearestPackageJsons(path.posix.join(baseDir, '..', 'another-module', 'index.js')), ).toHaveLength(0); }); - it('should log error when cannot access baseDir', async () => { + it('should log error when cannot access baseDir', () => { const baseDir = path.posix.join(toUnixPath(__dirname), 'fixtures'); console.error = jest.fn(); @@ -162,7 +162,7 @@ describe('initialize package.json files', () => { throw Error(`Cannot access ${dir}`); }); - await searchPackageJsonFiles(baseDir, ['']); + searchPackageJsonFiles(baseDir, ['']); expect(console.error).toHaveBeenCalledWith( `Error while searching for package.json files: Error: Cannot access ${baseDir}`, ); From 381eed79917435bca1bd790fbec128436325aac0 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 14 Nov 2023 11:28:17 +0100 Subject: [PATCH 11/16] wip --- packages/jsts/src/rules/S1874/cb.options.json | 2 +- packages/jsts/src/rules/S1874/cb.test.ts | 2 ++ packages/jsts/src/rules/S1874/package.json | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 packages/jsts/src/rules/S1874/package.json diff --git a/packages/jsts/src/rules/S1874/cb.options.json b/packages/jsts/src/rules/S1874/cb.options.json index e6e97861c5c..e91ee686b29 100644 --- a/packages/jsts/src/rules/S1874/cb.options.json +++ b/packages/jsts/src/rules/S1874/cb.options.json @@ -1,5 +1,5 @@ [ { - "react-version": "19.0" + "react-version": "15.0" } ] diff --git a/packages/jsts/src/rules/S1874/cb.test.ts b/packages/jsts/src/rules/S1874/cb.test.ts index 8fe3ff0701c..50335110b80 100644 --- a/packages/jsts/src/rules/S1874/cb.test.ts +++ b/packages/jsts/src/rules/S1874/cb.test.ts @@ -20,9 +20,11 @@ import { check } from '../tools'; import { rule } from './'; import path from 'path'; +import { searchPackageJsonFiles } from '@sonar/jsts'; const sonarId = path.basename(__dirname); describe(`Rule ${sonarId}`, () => { + searchPackageJsonFiles(__dirname, []); check(sonarId, rule, __dirname); }); diff --git a/packages/jsts/src/rules/S1874/package.json b/packages/jsts/src/rules/S1874/package.json new file mode 100644 index 00000000000..334c7c72be3 --- /dev/null +++ b/packages/jsts/src/rules/S1874/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "react": "19.0" + }, + "devDependencies": { + "react": "15.0" + } +} From da4a3de351d32324f63ec48a09a71e539ac3a786 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 14 Nov 2023 12:10:31 +0100 Subject: [PATCH 12/16] change react version --- packages/jsts/src/rules/S1874/cb.options.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jsts/src/rules/S1874/cb.options.json b/packages/jsts/src/rules/S1874/cb.options.json index e91ee686b29..e6e97861c5c 100644 --- a/packages/jsts/src/rules/S1874/cb.options.json +++ b/packages/jsts/src/rules/S1874/cb.options.json @@ -1,5 +1,5 @@ [ { - "react-version": "15.0" + "react-version": "19.0" } ] From 8826ac57470193f825abaa3f65dd8dfbf3943bc3 Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Wed, 15 Nov 2023 12:44:38 +0100 Subject: [PATCH 13/16] fix coverage --- .../jsts/postgraphql/javascript-S1874.json | 5 - jest.config.js | 4 +- .../rules/S1874/fixtures/react15/package.json | 8 + .../rules/S1874/fixtures/react19/package.json | 8 + packages/jsts/src/rules/S1874/unit.test.ts | 152 ++++++++++++++++++ 5 files changed, 169 insertions(+), 8 deletions(-) delete mode 100644 its/ruling/src/test/expected/jsts/postgraphql/javascript-S1874.json create mode 100644 packages/jsts/src/rules/S1874/fixtures/react15/package.json create mode 100644 packages/jsts/src/rules/S1874/fixtures/react19/package.json create mode 100644 packages/jsts/src/rules/S1874/unit.test.ts diff --git a/its/ruling/src/test/expected/jsts/postgraphql/javascript-S1874.json b/its/ruling/src/test/expected/jsts/postgraphql/javascript-S1874.json deleted file mode 100644 index 58bb03767ea..00000000000 --- a/its/ruling/src/test/expected/jsts/postgraphql/javascript-S1874.json +++ /dev/null @@ -1,5 +0,0 @@ -{ -"postgraphql:src/postgraphql/graphiql/src/index.js": [ -7 -] -} diff --git a/jest.config.js b/jest.config.js index cc1b1b64748..8db528fe4cf 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,9 +6,7 @@ module.exports = { moduleNameMapper: { '^@sonar/(\\w+)(.*)$': '/packages/$1/src$2', }, - modulePathIgnorePatterns: [ - '/packages/jsts/src/rules/S4328/fixtures/bom-package-json-project/package.json', - ], + modulePathIgnorePatterns: ['/packages/jsts/.*/package.json$'], testResultsProcessor: 'jest-sonar-reporter', transform: { '^.+\\.ts$': ['ts-jest', { tsconfig: 'packages/tsconfig.test.json' }], diff --git a/packages/jsts/src/rules/S1874/fixtures/react15/package.json b/packages/jsts/src/rules/S1874/fixtures/react15/package.json new file mode 100644 index 00000000000..ccd1fb755fa --- /dev/null +++ b/packages/jsts/src/rules/S1874/fixtures/react15/package.json @@ -0,0 +1,8 @@ +{ + "name": "react15-app", + "version": "1.0.0", + "author": "Your Name ", + "dependencies": { + "react": "15.0" + } +} diff --git a/packages/jsts/src/rules/S1874/fixtures/react19/package.json b/packages/jsts/src/rules/S1874/fixtures/react19/package.json new file mode 100644 index 00000000000..8576c32f5a7 --- /dev/null +++ b/packages/jsts/src/rules/S1874/fixtures/react19/package.json @@ -0,0 +1,8 @@ +{ + "name": "react15-app", + "version": "1.0.0", + "author": "Your Name ", + "dependencies": { + "react": "19.0" + } +} diff --git a/packages/jsts/src/rules/S1874/unit.test.ts b/packages/jsts/src/rules/S1874/unit.test.ts new file mode 100644 index 00000000000..c66d0ddd2d0 --- /dev/null +++ b/packages/jsts/src/rules/S1874/unit.test.ts @@ -0,0 +1,152 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { RuleTester } from 'eslint'; +import { rule } from './'; +import path from 'path'; +import { getAllPackageJsons, searchPackageJsonFiles } from '@sonar/jsts'; + +//reset and search package.json files in rule dir +getAllPackageJsons().clear(); +searchPackageJsonFiles(__dirname, []); + +const fixtures = path.join(__dirname, 'fixtures'); +const filenameReact15 = path.join(fixtures, 'react15/file.js'); + +const tsParserPath = require.resolve('@typescript-eslint/parser'); +const ruleTester = new RuleTester({ + parser: tsParserPath, + parserOptions: { ecmaVersion: 2018, sourceType: 'module' }, +}); + +ruleTester.run('React15', rule, { + valid: [ + { + code: ` +import React from 'react'; +import ReactDOM from 'react-dom'; + +React.createClass({ /* Class object */ }); + +const propTypes = { + foo: React.PropTypes.bar, +}; + +//Any factories under React.DOM +React.DOM.div(); + +class ApiCall extends React.Component { +// old lifecycles (since React 16.9) + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} +} + +// React 18 deprecations +ReactDOM.render(
, container); + +ReactDOM.hydrate(
, container); + +ReactDOM.unmountComponentAtNode(container); + +ReactDOMServer.renderToNodeStream(element); +`, + filename: filenameReact15, + }, + ], + invalid: [ + { + code: ` +import React from 'react'; +import ReactDOM from 'react-dom'; + +React.render(, root); + +React.unmountComponentAtNode(root); + +React.findDOMNode(this.refs.foo); + +React.renderToString(); + +React.renderToStaticMarkup(); +`, + filename: filenameReact15, + errors: 5, + }, + ], +}); + +const filenameReact19 = path.join(fixtures, 'react19/file.js'); + +ruleTester.run('React19', rule, { + valid: [ + { + code: ` +import React from 'react'; +import ReactDOM from 'react-dom'; +`, + filename: filenameReact19, + }, + ], + invalid: [ + { + code: ` +import React from 'react'; +import ReactDOM from 'react-dom'; + +React.render(, root); + +React.unmountComponentAtNode(root); + +React.findDOMNode(this.refs.foo); + +React.renderToString(); + +React.renderToStaticMarkup(); + +React.createClass({ /* Class object */ }); + +const propTypes = { + foo: React.PropTypes.bar, +}; + +//Any factories under React.DOM +React.DOM.div(); + +class ApiCall extends React.Component { +// old lifecycles (since React 16.9) + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} +} + +// React 18 deprecations +ReactDOM.render(
, container); + +ReactDOM.hydrate(
, container); + +ReactDOM.unmountComponentAtNode(container); + +ReactDOMServer.renderToNodeStream(element); +`, + filename: filenameReact19, + errors: 15, + }, + ], +}); From 2b6c73136efdaff9349d9a9cd39ef60897621ff4 Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Wed, 15 Nov 2023 13:07:31 +0100 Subject: [PATCH 14/16] fix jest module ignores --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 8db528fe4cf..6facd701dd8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,7 @@ module.exports = { moduleNameMapper: { '^@sonar/(\\w+)(.*)$': '/packages/$1/src$2', }, - modulePathIgnorePatterns: ['/packages/jsts/.*/package.json$'], + modulePathIgnorePatterns: ['/packages/jsts/src/rules/.*/package.json$'], testResultsProcessor: 'jest-sonar-reporter', transform: { '^.+\\.ts$': ['ts-jest', { tsconfig: 'packages/tsconfig.test.json' }], From facc22b738ea31d59a988513ab3c7d7b538bb52c Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Wed, 15 Nov 2023 13:39:33 +0100 Subject: [PATCH 15/16] improve coverage --- .../S1874/fixtures/noreact1/package.json | 5 ++ .../S1874/fixtures/noreact2/package.json | 11 ++++ .../rules/S1874/fixtures/react19/package.json | 4 +- packages/jsts/src/rules/S1874/unit.test.ts | 62 +++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 packages/jsts/src/rules/S1874/fixtures/noreact1/package.json create mode 100644 packages/jsts/src/rules/S1874/fixtures/noreact2/package.json diff --git a/packages/jsts/src/rules/S1874/fixtures/noreact1/package.json b/packages/jsts/src/rules/S1874/fixtures/noreact1/package.json new file mode 100644 index 00000000000..eee4489a20e --- /dev/null +++ b/packages/jsts/src/rules/S1874/fixtures/noreact1/package.json @@ -0,0 +1,5 @@ +{ + "name": "no-react-app", + "version": "1.0.0", + "author": "Your Name " +} diff --git a/packages/jsts/src/rules/S1874/fixtures/noreact2/package.json b/packages/jsts/src/rules/S1874/fixtures/noreact2/package.json new file mode 100644 index 00000000000..9dbf3dc8436 --- /dev/null +++ b/packages/jsts/src/rules/S1874/fixtures/noreact2/package.json @@ -0,0 +1,11 @@ +{ + "name": "no-react-app2", + "version": "1.0.0", + "author": "Your Name ", + "dependencies": { + + }, + "devDependencies": { + + } +} diff --git a/packages/jsts/src/rules/S1874/fixtures/react19/package.json b/packages/jsts/src/rules/S1874/fixtures/react19/package.json index 8576c32f5a7..622e19c0203 100644 --- a/packages/jsts/src/rules/S1874/fixtures/react19/package.json +++ b/packages/jsts/src/rules/S1874/fixtures/react19/package.json @@ -1,8 +1,8 @@ { - "name": "react15-app", + "name": "react19-app", "version": "1.0.0", "author": "Your Name ", - "dependencies": { + "devDependencies": { "react": "19.0" } } diff --git a/packages/jsts/src/rules/S1874/unit.test.ts b/packages/jsts/src/rules/S1874/unit.test.ts index c66d0ddd2d0..7e0c5ea8174 100644 --- a/packages/jsts/src/rules/S1874/unit.test.ts +++ b/packages/jsts/src/rules/S1874/unit.test.ts @@ -150,3 +150,65 @@ ReactDOMServer.renderToNodeStream(element); }, ], }); + +shouldRaiseAllIssues(path.join(fixtures, 'noreact1/file.js')); +shouldRaiseAllIssues(path.join(fixtures, 'noreact2/file.js')); + +function shouldRaiseAllIssues(filename) { + ruleTester.run(`No React ${filename}`, rule, { + valid: [ + { + code: ` +import React from 'react'; +import ReactDOM from 'react-dom'; +`, + filename, + }, + ], + invalid: [ + { + code: ` +import React from 'react'; +import ReactDOM from 'react-dom'; + +React.render(, root); + +React.unmountComponentAtNode(root); + +React.findDOMNode(this.refs.foo); + +React.renderToString(); + +React.renderToStaticMarkup(); + +React.createClass({ /* Class object */ }); + +const propTypes = { + foo: React.PropTypes.bar, +}; + +//Any factories under React.DOM +React.DOM.div(); + +class ApiCall extends React.Component { +// old lifecycles (since React 16.9) + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} +} + +// React 18 deprecations +ReactDOM.render(
, container); + +ReactDOM.hydrate(
, container); + +ReactDOM.unmountComponentAtNode(container); + +ReactDOMServer.renderToNodeStream(element); +`, + filename, + errors: 15, + }, + ], + }); +} From f61a0f6dc7ce33a621a1e9c0ee8068e299aa9b5e Mon Sep 17 00:00:00 2001 From: Victor Diez Date: Wed, 15 Nov 2023 14:56:10 +0100 Subject: [PATCH 16/16] use beforeAll instead of synchronously searching package.json files inside describe --- packages/jsts/src/rules/S1874/cb.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/jsts/src/rules/S1874/cb.test.ts b/packages/jsts/src/rules/S1874/cb.test.ts index 50335110b80..b08d5821fc8 100644 --- a/packages/jsts/src/rules/S1874/cb.test.ts +++ b/packages/jsts/src/rules/S1874/cb.test.ts @@ -20,11 +20,16 @@ import { check } from '../tools'; import { rule } from './'; import path from 'path'; -import { searchPackageJsonFiles } from '@sonar/jsts'; +import { getAllPackageJsons, searchPackageJsonFiles } from '@sonar/jsts'; const sonarId = path.basename(__dirname); describe(`Rule ${sonarId}`, () => { - searchPackageJsonFiles(__dirname, []); + beforeEach(() => { + searchPackageJsonFiles(__dirname, []); + }); + afterAll(() => { + getAllPackageJsons().clear(); + }); check(sonarId, rule, __dirname); });