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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ parserPlugins: [
proposal: 'hack',
},
],
]
];
```

### `sortImports`
Expand Down
114 changes: 6 additions & 108 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,10 @@ const ENVIRONMENTS = {
meteor: meteorEnvironment,
};

function checkForUnknownConfiguration(config: Object): Array<string> {
const messages = [];

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

return messages;
return result.messages;
}

function checkForDeprecatedConfiguration(config: Object): Array<string> {
Expand Down Expand Up @@ -242,7 +140,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