Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Forbid self package import #1498

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module.exports = {
root: true,
extends: ['@deephaven/eslint-config'],
// The `deephaven` eslint plugin is defined in `packages/eslint-config/plugin`
plugins: ['deephaven'],
ignorePatterns: ['packages/golden-layout/*', 'jest.config.*'],
overrides: [
{
Expand All @@ -9,6 +11,9 @@ module.exports = {
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
tsconfigRootDir: __dirname,
},
rules: {
'deephaven/no-self-package-import': 'error',
},
},
],
};
16 changes: 15 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions packages/eslint-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ In your package.json, add a `eslintConfig` configuration:
"eslintConfig": { "extends": "@deephaven/eslint-config" }
```

## Deephaven Eslint Plugin (eslint-plugin-deephaven)
The `deephaven` eslint plugin is defined inside of the `plugin` folder. There is
a `devDependency` mapping in `package.json` which allows it to be consumed in an
eslint config file using the name `deephaven`.

```json
"devDependencies": {
"eslint-plugin-deephaven": "file:./plugin"
},
```

e.g.
```javascript
module.exports = {
root: true,
plugins: ['deephaven'],
}
```

# Legal Notices

Deephaven Data Labs and any contributors grant you a license to the content of this repository under the Apache 2.0 License, see the [LICENSE](../../LICENSE) file.
3 changes: 3 additions & 0 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"eslint-config-prettier": "8.3.0",
"eslint-config-react-app": "7.0.0"
},
"devDependencies": {
"eslint-plugin-deephaven": "file:./plugin"
},
Comment on lines +17 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you don't need to do it this way. You can import the plugin directly

https://eslint.org/docs/latest/extend/custom-rule-tutorial#step-7-bundle-the-custom-rule-in-a-plugin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work if you try to add it to an array of plugins since the plugins array only supports string names. Granted the current config only has 1 plugin, this will be more future proof.

That said if you find a way to get it working alongside other plugins, I'd love to see how. I was not able to get it working.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"peerDependencies": {
"@typescript-eslint/eslint-plugin": "^5.46.0",
"@typescript-eslint/parser": "^5.46.0",
Expand Down
7 changes: 7 additions & 0 deletions packages/eslint-config/plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const noSelfPackageImportRule = require('./no-self-package-import');

const plugin = {
rules: { 'no-self-package-import': noSelfPackageImportRule },
};

module.exports = plugin;
60 changes: 60 additions & 0 deletions packages/eslint-config/plugin/no-self-package-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Derive the `@deephaven` package name from the given path.
* Note that this assumes that the package folder name uses the same naming
* convention as the package.json `name` field.
*/
function deriveDeephavenPackageNameFromPath(path) {
// In the off chance that there is a packages folder higher up in the path,
// find the deepest one.
const packageFolderNameI = path.lastIndexOf('packages');

if (packageFolderNameI === -1) {
return null;
}

// Find the path token immediately after the `packages` folder.
const [packageFolderName] = path
.substring(packageFolderNameI + 'packages'.length + 1)
.split('/');

if (packageFolderName === '') {
return null;
}

return `@deephaven/${packageFolderName}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I'm not crazy about is this rule is not generic; even when we include this eslint config from Enterprise it won't technically be correct. We should also be using the path package to resolve paths rather than manipulating the string directly, as there's probably some edge cases you're missing here (e.g. what if I had Enterprise using this eslint config and it was in a folder like ~/deephaven/packages/app/..., then it would think the package name was @deephaven/app).
Getting the package name from the package.json should be doable, though I do also wonder about performance and how we can cache results (since this will be called on every import in every file, don't want to have to traverse and read package.json every time...). I'd rather check in the fix first without the eslint rule than check it in with specific @deephaven logic.

Copy link
Contributor Author

@bmingles bmingles Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mofojed This "shouldn't" ever actually be used in enterprise since it doesn't really apply. It is currently consumed via the top level Community package so doesn't get pulled in with the shared config. It really is scoped to @deephaven community packages. Does this alleviate any of your concern?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually second guessing the approach of using this plugin now. Since it's not actually configured in the shared package and not really useful elsewhere, maybe the top level community config should be fully responsible for it. Would be easy to just get the list of packages and build a rule array there

}

/**
* Custom eslint rule that forbids importing from the same `@deephaven` package
* that owns the module. Note that this rule is only useful within the `@deephaven`
* web-client-ui monorepo.
*/
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'Forbid importing from the same `@deephaven` package that owns this module.',
},
},
create: function create(context) {
const filePath = context.getFilename();

const packageName = deriveDeephavenPackageNameFromPath(filePath);
if (packageName == null) {
return {};
}

return {
ImportDeclaration(node) {
if (node.source.value.includes(packageName)) {
context.report({
node,
message:
'Forbid importing from the same `@deephaven` package that owns this module.',
});
}
},
};
},
};
5 changes: 1 addition & 4 deletions packages/jsapi-utils/src/SessionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ import type {
IdeConnection,
IdeSession,
} from '@deephaven/jsapi-types';
import {
requestParentResponse,
SESSION_DETAILS_REQUEST,
} from '@deephaven/jsapi-utils';
import Log from '@deephaven/log';
import shortid from 'shortid';
import { requestParentResponse, SESSION_DETAILS_REQUEST } from './MessageUtils';
import NoConsolesError, { isNoConsolesError } from './NoConsolesError';

const log = Log.module('SessionUtils');
Expand Down