Skip to content

Commit

Permalink
feat(lint): Add a set of eslint rule that enforce package import rules
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen authored and mergify[bot] committed Jan 28, 2020
1 parent e15b796 commit 87542a8
Show file tree
Hide file tree
Showing 21 changed files with 529 additions and 12 deletions.
3 changes: 3 additions & 0 deletions packages/eslint-plugin/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Built lib files
/**/modules/*/lib/**
/**/*.spec.*
4 changes: 4 additions & 0 deletions packages/eslint-plugin/base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ module.exports = {
plugins: ['@typescript-eslint', '@spinnaker/eslint-plugin'],
extends: ['eslint:recommended', 'prettier', 'prettier/@typescript-eslint', 'plugin:@typescript-eslint/recommended'],
rules: {
'@spinnaker/import-from-alias-not-npm': 2,
'@spinnaker/import-from-npm-not-alias': 2,
'@spinnaker/import-from-npm-not-relative': 2,
'@spinnaker/import-relative-within-subpackage': 2,
'@spinnaker/ng-no-component-class': 2,
'@spinnaker/ng-no-module-export': 2,
'@spinnaker/ng-no-require-angularjs': 2,
Expand Down
4 changes: 4 additions & 0 deletions packages/eslint-plugin/eslint-plugin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module.exports = {
rules: {
'import-from-alias-not-npm': require('./rules/import-from-alias-not-npm'),
'import-from-npm-not-alias': require('./rules/import-from-npm-not-alias'),
'import-from-npm-not-relative': require('./rules/import-from-npm-not-relative'),
'import-relative-within-subpackage': require('./rules/import-relative-within-subpackage'),
'ng-no-component-class': require('./rules/ng-no-component-class'),
'ng-no-module-export': require('./rules/ng-no-module-export'),
'ng-no-require-angularjs': require('./rules/ng-no-require-angularjs'),
Expand Down
12 changes: 7 additions & 5 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
{
"name": "@spinnaker/eslint-plugin",
"version": "1.0.0",
"version": "1.0.2",
"main": "eslint-plugin.js",
"scripts": {
"test": "jest"
"test": "jest",
"test:debug": "node --inspect ./node_modules/.bin/jest --runInBand --watch"
},
"dependencies": {
"jest": "^24.9.0"
"jest": "^24.9.0",
"lodash": "^4.17.15"
},
"peerDependencies": {
"peerDependencies": {
"@typescript-eslint/eslint-plugin": "^1.3.0",
"@typescript-eslint/parser": "^1.3.0",
"@typescript-eslint/parser": "^1.3.0",
"eslint": ">=5",
"eslint-config-prettier": "^4.0.0"
}
Expand Down
50 changes: 50 additions & 0 deletions packages/eslint-plugin/rules/import-from-alias-not-npm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';
const { getSourceFileDetails, getImportFromNpm } = require('../utils/import-aliases');

/**
* A group of rules that enforce spinnaker ES6 import alias conventions.
*
* Source code in a package (i.e., `core`) should not import from `@spinnaker/core`
*
* @version 0.1.0
* @category conventions
*/
const rule = function(context) {
const { ownPackage } = getSourceFileDetails(context.getFilename());
if (!ownPackage) {
return {};
}

return {
ImportDeclaration: function(node) {
if (node.source.type !== 'Literal' || !node.source.value) {
return;
}

const importString = node.source.value;
const importFromNpm = getImportFromNpm(importString);
if (!importFromNpm || importFromNpm.pkg !== ownPackage) {
return;
}

const { pkg, importPathWithSlash } = importFromNpm;
const message =
`Do not use ${importString} to import from ${ownPackage} from code inside ${ownPackage}. ` +
` Instead, use the ${pkg} alias or a relative import`;

const fix = fixer => fixer.replaceText(node.source, `'${pkg}${importPathWithSlash}'`);
context.report({ fix, node, message });
},
};
};

module.exports = {
meta: {
type: 'problem',
docs: {
description: `Enforces spinnaker ES6 import conventions for package aliases`,
},
fixable: 'code',
},
create: rule,
};
53 changes: 53 additions & 0 deletions packages/eslint-plugin/rules/import-from-npm-not-alias.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';
const { getAliasImport, getSourceFileDetails, getAllSpinnakerPackages } = require('../utils/import-aliases');

/**
* A group of rules that enforce spinnaker ES6 import alias conventions.
*
* Source code in a package (i.e., `amazon`) should not import from a different package using an alias (i.e., `core/`)
* Instead, it should import from `@spinnaker/core`
*
* @version 0.1.0
* @category conventions
*/
const rule = function(context) {
const sourceFile = context.getFilename();
const { modulesPath, ownPackage } = getSourceFileDetails(sourceFile);
if (!ownPackage) {
return {};
}
const allSpinnakerPackages = getAllSpinnakerPackages(modulesPath);

return {
ImportDeclaration: function(node) {
if (node.source.type !== 'Literal' || !node.source.value) {
return;
}

const importString = node.source.value;
const aliasImport = getAliasImport(allSpinnakerPackages, importString);

if (!aliasImport || aliasImport.pkg === ownPackage) {
return;
}
const { pkg } = aliasImport;
const message =
`Do not use an alias to import from ${pkg} from code inside ${ownPackage}.` +
` Instead, use the npm package @spinnaker/${pkg}`;

const fix = fixer => fixer.replaceText(node.source, `'@spinnaker/${pkg}'`);
context.report({ fix, node, message });
},
};
};

const importAliasesRule = (module.exports = {
meta: {
type: 'problem',
docs: {
description: `Enforces spinnaker ES6 import conventions for package aliases`,
},
fixable: 'code',
},
create: rule,
});
53 changes: 53 additions & 0 deletions packages/eslint-plugin/rules/import-from-npm-not-relative.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';
const { getSourceFileDetails, getRelativeImport } = require('../utils/import-aliases');

/**
* A group of rules that enforce spinnaker ES6 import alias conventions.
*
* Source code in a package (i.e., `amazon`) should not import from package (i.e., `core`) using a relative path.
* Instead, it should import from `@spinnaker/core`
*
* @version 0.1.0
* @category conventions
*/
const rule = function(context) {
const sourceFile = context.getFilename();
const { modulesPath, sourceDirectory, ownPackage } = getSourceFileDetails(sourceFile);
if (!ownPackage) {
return {};
}

return {
ImportDeclaration: function(node) {
if (node.source.type !== 'Literal' || !node.source.value) {
return;
}

const importString = node.source.value;
const relativeImport = getRelativeImport(sourceDirectory, modulesPath, importString);

if (!relativeImport || relativeImport.pkg === ownPackage) {
return;
}

const { pkg } = relativeImport;
const message =
`Do not use a relative import to import from ${pkg} from code inside ${ownPackage}.` +
` Instead, use the npm package @spinnaker/${pkg}`;

const fix = fixer => fixer.replaceText(node.source, `'@spinnaker/${pkg}'`);
context.report({ fix, node, message });
},
};
};

module.exports = {
meta: {
type: 'problem',
docs: {
description: `Enforces spinnaker ES6 import conventions for package aliases`,
},
fixable: 'code',
},
create: rule,
};
65 changes: 65 additions & 0 deletions packages/eslint-plugin/rules/import-relative-within-subpackage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use strict';
const path = require('path');
const { getAliasImport, getSourceFileDetails, getAllSpinnakerPackages } = require('../utils/import-aliases');

/**
* A group of rules that enforce spinnaker ES6 import alias conventions.
*
* Source code in a package (i.e., `core/presentation` should not import from the same subpackage using an alias
* `core/presentation`.
* Instead, it should import relatively `../../path/file`
*
* @version 0.1.0
* @category conventions
*/
const rule = function(context) {
const sourceFile = context.getFilename();
const { modulesPath, ownPackage, ownSubPackage, filePath } = getSourceFileDetails(sourceFile);
if (!ownPackage) {
return {};
}
const allSpinnakerPackages = getAllSpinnakerPackages(modulesPath);

return {
ImportDeclaration: function(node) {
if (node.source.type !== 'Literal' || !node.source.value) {
return;
}

const importString = node.source.value;
const aliasImport = getAliasImport(allSpinnakerPackages, importString);
if (
!aliasImport ||
aliasImport.pkg !== ownPackage ||
aliasImport.subPkg !== ownSubPackage ||
aliasImport.importPath === aliasImport.subPkg // don't handle import from 'core/subpackage' in this rule
) {
return;
}

const { pkg, subPkg, importPath } = aliasImport;

const message =
`Do not use an alias to import from ${pkg}/${subPkg} from code inside ${pkg}/${subPkg}.` +
` Instead, use a relative import`;
const fix = fixer => {
const relativeDir = path.relative(path.dirname(filePath), path.dirname(importPath)) || '.';
let newPath = path.join(relativeDir, path.basename(importPath));
newPath = newPath.match(/^\.?\.\//) ? newPath : './' + newPath;
return fixer.replaceText(node.source, `'${newPath}'`);
};
context.report({ fix, node, message });
},
};
};

const importAliasesRule = (module.exports = {
meta: {
type: 'problem',
docs: {
description: `Enforces spinnaker ES6 import conventions for package aliases`,
},
fixable: 'code',
},
create: rule,
});
1 change: 0 additions & 1 deletion packages/eslint-plugin/test.eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"rules": {
"prefer-bare-module": 2
},
"parserOptions": {
"ecmaVersion": 8,
Expand Down
24 changes: 24 additions & 0 deletions packages/eslint-plugin/test/import-from-alias-not-npm.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const ruleTester = require('../utils/ruleTester');
const rule = require('../rules/import-from-alias-not-npm');

ruleTester.run('import-from-alias-not-npm', rule, {
valid: [
{
filename: '/root/spinnaker/deck/app/scripts/modules/amazon/package/amazon_source_file.ts',
code: `import { Anything } from '@spinnaker/core';`,
},
],

invalid: [
{
filename: '/root/spinnaker/deck/app/scripts/modules/core/package/core_source_file.ts',
code: `import { Anything } from '@spinnaker/core';`,
output: `import { Anything } from 'core';`,
errors: [
'Do not use @spinnaker/core to import from core from code inside core. Instead, use the core alias or a relative import',
],
},
],
});
27 changes: 27 additions & 0 deletions packages/eslint-plugin/test/import-from-npm-not-alias.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';
const mockModule = require('../utils/mockModule');
const mock = mockModule('../utils/import-aliases');
mock.getAllSpinnakerPackages.mockImplementation(() => ['core', 'amazon', 'titus', 'docker']);

const ruleTester = require('../utils/ruleTester');
const rule = require('../rules/import-from-npm-not-alias');

ruleTester.run('import-from-npm-not-alias', rule, {
valid: [
{
filename: '/root/spinnaker/deck/app/scripts/modules/amazon/package/amazon_source_file.ts',
code: `import { Anything } from 'amazon/otherpackage';`,
},
],

invalid: [
{
filename: '/root/spinnaker/deck/app/scripts/modules/amazon/package/amazon_source_file.ts',
code: `import { Anything } from 'core/otherpackage';`,
output: `import { Anything } from '@spinnaker/core';`,
errors: [
'Do not use an alias to import from core from code inside amazon. Instead, use the npm package @spinnaker/core',
],
},
],
});
24 changes: 24 additions & 0 deletions packages/eslint-plugin/test/import-from-npm-not-relative.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const ruleTester = require('../utils/ruleTester');
const rule = require('../rules/import-from-npm-not-relative');

ruleTester.run('import-from-npm-not-relative', rule, {
valid: [
{
filename: '/root/spinnaker/deck/app/scripts/modules/amazon/package/amazon_source_file.ts',
code: `import { Anything } from '../othersubpackage/file2';`,
},
],

invalid: [
{
filename: '/root/spinnaker/deck/app/scripts/modules/amazon/package/amazon_source_file.ts',
code: `import { Anything } from '../../core/subpackage/file2';`,
output: `import { Anything } from '@spinnaker/core';`,
errors: [
'Do not use a relative import to import from core from code inside amazon. Instead, use the npm package @spinnaker/core',
],
},
],
});
Loading

0 comments on commit 87542a8

Please sign in to comment.