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

Add configuration validation and refactor handling of unknown identifiers #612

Merged
merged 9 commits into from
Feb 7, 2024
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,6 @@ Available plugins are over at [Babel: Plugins List](https://babeljs.io/docs/plug

#### Example: Remove all preconfigured defaults

```javascript
mikabytes marked this conversation as resolved.
Show resolved Hide resolved
parserPlugins: []
```

Expand Down Expand Up @@ -571,7 +570,7 @@ parserPlugins: [
proposal: 'hack',
},
],
]
];
```

### `sortImports`
Expand Down
112 changes: 6 additions & 106 deletions lib/Configuration.js
Original file line number Diff line number Diff line change
@@ -1,119 +1,23 @@
// @flow

import crypto from 'crypto';
import os from 'os';
import path from 'path';
import has from 'lodash/has';

import globals from 'globals';
import semver from 'semver';

import FileUtils from './FileUtils';
import JsModule from './JsModule';
import findPackageDependencies from './findPackageDependencies';
import meteorEnvironment from './environments/meteorEnvironment';
import nodeEnvironment from './environments/nodeEnvironment';
import normalizePath from './normalizePath';
import version from './version';
import { DEFAULT_PARSER_PLUGINS } from './parse.js';
import { validate, getDefaultConfig } from './configurationSchema.js';

const JSON_CONFIG_FILE = '.importjs.json';
const JS_CONFIG_FILES = ['.importjs.js', '.importjs.cjs', '.importjs.mjs'];

function findGlobalsFromEnvironments(
environments: Array<string>,
): Array<string> {
const result = Object.keys(globals.builtin);

environments.forEach((environment: string) => {
const envGlobals = globals[environment];
if (!envGlobals) {
return;
}
result.push(...Object.keys(envGlobals));
});
return result;
}

const DEFAULT_CONFIG = {
aliases: {},
declarationKeyword: 'import',
cacheLocation: ({ config }: Object): string => {
const hash = crypto
.createHash('md5')
.update(`${config.workingDirectory}-v4`)
.digest('hex');
return path.join(os.tmpdir(), `import-js-${hash}.db`);
},
coreModules: [],
namedExports: {},
environments: [],
excludes: [],
globals: ({ config }: Object): Array<string> =>
findGlobalsFromEnvironments(config.get('environments')),
groupImports: true,
ignorePackagePrefixes: [],
importDevDependencies: false,
importFunction: 'require',
importStatementFormatter: ({ importStatement }: Object): string =>
importStatement,
logLevel: 'info',
maxLineLength: 80,
minimumVersion: '0.0.0',
moduleNameFormatter: ({ moduleName }: Object): string => moduleName,
moduleSideEffectImports: (): Array<string> => [],
sortImports: true,
emptyLineBetweenGroups: true,
stripFileExtensions: ['.js', '.jsx', '.ts', '.tsx'],
danglingCommas: true,
tab: ' ',
useRelativePaths: true,
packageDependencies: ({ config }: Object): Set<string> =>
findPackageDependencies(
config.workingDirectory,
config.get('importDevDependencies'),
),
// Default configuration options, and options inherited from environment
// configuration are overridden if they appear in user config. Some options,
// however, get merged with the parent configuration. This list specifies which
// ones are merged.
mergableOptions: {
aliases: true,
coreModules: true,
namedExports: true,
globals: true,
},
parserPlugins: DEFAULT_PARSER_PLUGINS,
};

const KNOWN_CONFIGURATION_OPTIONS = [
'aliases',
'cacheLocation',
'coreModules',
'declarationKeyword',
'environments',
'excludes',
'globals',
'groupImports',
'ignorePackagePrefixes',
'importDevDependencies',
'importFunction',
'importStatementFormatter',
'logLevel',
'maxLineLength',
'minimumVersion',
'moduleNameFormatter',
'moduleSideEffectImports',
'namedExports',
'sortImports',
'stripFileExtensions',
'tab',
'useRelativePaths',
'mergableOptions',
'danglingCommas',
'emptyLineBetweenGroups',
'parserPlugins',
];
const DEFAULT_CONFIG = getDefaultConfig();

const DEPRECATED_CONFIGURATION_OPTIONS = [];

Expand All @@ -122,16 +26,12 @@ const ENVIRONMENTS = {
meteor: meteorEnvironment,
};

function checkForUnknownConfiguration(config: Object): Array<string> {
function checkConfiguration(config: Object): Array<string> {
const messages = [];
mikabytes marked this conversation as resolved.
Show resolved Hide resolved

Object.keys(config).forEach((option: string) => {
if (KNOWN_CONFIGURATION_OPTIONS.indexOf(option) === -1) {
messages.push(`Unknown configuration: \`${option}\``);
}
});
const result = validate(config);

return messages;
return result.messages;
}

function checkForDeprecatedConfiguration(config: Object): Array<string> {
Expand Down Expand Up @@ -242,7 +142,7 @@ export default class Configuration {

if (userConfig) {
this.configs.push(userConfig);
this.messages.push(...checkForUnknownConfiguration(userConfig));
this.messages.push(...checkConfiguration(userConfig));
this.messages.push(...checkForDeprecatedConfiguration(userConfig));

// Add configurations for the environments specified in the user config
Expand Down
2 changes: 1 addition & 1 deletion lib/__tests__/ImportStatements-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ describe('ImportStatements', () => {
beforeEach(() => {
FileUtils.__setFile(path.join(process.cwd(), '.importjs.js'), {
environments: ['meteor'],
packageDependencies: new Set(['meteor/bar']),
packageDependencies: ['meteor/bar'],
});
});

Expand Down
45 changes: 45 additions & 0 deletions lib/__tests__/configurationSchema-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { validate, getDefaultConfig } from '../configurationSchema.js';

it('should export defaults as an object', () => {
const config = getDefaultConfig();

expect(config.aliases).toEqual({});
});

it('should validate successfully', () => {
const result = validate({
aliases: {
_: 'third-party-libs/underscore',
},
});
expect(result.error).toEqual(false);
expect(result.messages).toEqual([]);
});

it('should notify about unknown identifiers, and remove them', () => {
const data = {
thisAintRight: 'better fail',
};
const result = validate(data);
expect(result.error).toEqual(true);
expect(result.messages[0]).toEqual('Unknown configuration: `thisAintRight`');
expect(data.hasOwnProperty('thisAintRight')).toBe(false);
});

it('should handle functions', () => {
const result = validate({
aliases: () => ({ _: 'third-party-libs/underscore' }),
});
expect(result.error).toEqual(false);
});

it('should notify about invalid identifiers, and remove them', () => {
const data = {
aliases: 123,
};
const result = validate(data);
expect(result.error).toEqual(true);
expect(result.messages.length).toEqual(1);
expect(result.messages[0]).toEqual('Invalid configuration: `aliases`');
expect(data.hasOwnProperty('aliases')).toBe(false);
});
Loading
Loading