Skip to content

Commit

Permalink
new rule exports-valid
Browse files Browse the repository at this point in the history
  • Loading branch information
mightyiam committed Sep 19, 2020
1 parent fefc0b5 commit e612831
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 57 deletions.
126 changes: 69 additions & 57 deletions src/rules/exports-valid.js
Expand Up @@ -54,90 +54,102 @@ const validateFallbacks = (fallbacks) => {
return true;
};

// eslint-disable-next-line max-lines-per-function
const lint = (packageJsonData, severity, config = {conditions: []}) => {
const conditions = [...(config.conditions || []), 'default'];
const validateStringValue = (parentKey, value) => {
if (!isValidPath(value)) {
return {error: 'invalidPath', str: value};
}

if (!exists(packageJsonData, nodeName)) return true;
if (parentKey.endsWith('/') && !value.endsWith('/')) {
return {error: 'folderMappedToFile', str: parentKey};
}

// eslint-disable-next-line complexity,max-statements,max-lines-per-function
const traverse = (parentKey, parentType, exports) => {
if (typeof exports === 'string') {
if (!isValidPath(exports)) {
return {error: 'invalidPath', str: exports};
}
return true;
};

if (parentKey.endsWith('/') && !exports.endsWith('/')) {
return {error: 'folderMappedToFile', str: parentKey};
}
const validateObject = (parentKey, parentType, object, config) => {
// either a paths object or a conditions object
let objectType;

return true;
}
const entries = Object.entries(object);

if (Array.isArray(exports)) {
// https://nodejs.org/api/esm.html#esm_package_exports_fallbacks
// eslint-disable-next-line no-restricted-syntax
return validateFallbacks(exports);
}
for (let i = 0; i < entries.length; i += 1) {
const [key, value] = entries[i];

if (!isPlainObj(exports)) {
return {error: 'unexpectedType', str: typeof exports};
}
if (isValidPathKey(key)) {
if (objectType === 'conditions') {
return {error: 'pathInConditions', str: key};
}

// either a paths object or a conditions object
let objectType;
if (parentType === 'paths') {
return {error: 'nestedPaths', str: parentKey};
}

const entries = Object.entries(exports);
objectType = 'paths';

for (let i = 0; i < entries.length; i += 1) {
const [key, value] = entries[i];
// eslint-disable-next-line no-use-before-define
const result = traverse(key, objectType, value, config);

if (isValidPathKey(key)) {
if (objectType === 'conditions') {
return {error: 'pathInConditions', str: key};
}
if (result !== true) return result;
} else {
// `key` interpreted as a condition
if (!config.conditions.includes(key)) {
return {error: 'unsupportedCondition', str: key};
}

if (parentType === 'paths') {
return {error: 'nestedPaths', str: parentKey};
}
if (objectType === 'paths') {
return {error: 'conditionInPaths', str: key};
}

objectType = 'paths';
objectType = 'conditions';
if (key === 'default' && i + 1 < entries.length) {
return {error: 'defaultConditionNotLast'};
}

const result = traverse(key, objectType, value);
// eslint-disable-next-line no-use-before-define
const result = traverse(key, objectType, value, config);

if (result !== true) return result;
} else {
// `key` interpreted as a condition
if (!conditions.includes(key)) {
return {error: 'unsupportedCondition', str: key};
}
if (result !== true) return result;
}
}

if (objectType === 'paths') {
return {error: 'conditionInPaths', str: key};
}
return true;
};

objectType = 'conditions';
if (key === 'default' && i + 1 < entries.length) {
return {error: 'defaultConditionNotLast'};
}
const traverse = (parentKey, parentType, node, config) => {
if (typeof node === 'string') {
return validateStringValue(parentKey, node);
}

const result = traverse(key, objectType, value);
if (Array.isArray(node)) {
// https://nodejs.org/api/esm.html#esm_package_exports_fallbacks
return validateFallbacks(node);
}

if (result !== true) return result;
}
}
if (!isPlainObj(node)) {
return {error: 'unexpectedType', str: typeof node};
}

return validateObject(parentKey, parentType, node, config);
};

return true;
const lint = (packageJsonData, severity, providedConfig) => {
const config = {
conditions: [],
...providedConfig,
};

const result = traverse(nodeName, 'root', packageJsonData[nodeName]);
config.conditions.push('default');

if (!exists(packageJsonData, nodeName)) return true;

const result = traverse(nodeName, 'root', packageJsonData[nodeName], config);

if (result !== true) {
const message = {
invalidPath: `invalid path \`${result.str}\` must start with \`./\``,
pathInConditions: `found path key \`${result.str}\` in a conditions object`,
nestedPaths: `key \`${result.str}\` has paths object vaule but only conditions may be nested`,
unsupportedCondition: `condition \`${result.str}\` not in supported conditions \`${conditions}\``,
unsupportedCondition: `condition \`${result.str}\` not in supported conditions \`${config.conditions}\``,
conditionInPaths: `found condition key \`${result.str}\` in a paths object`,
unexpectedType: `unexpected \`${result.str}\``,
defaultConditionNotLast: 'condition `default` must be the last key',
Expand Down
1 change: 1 addition & 0 deletions test/unit/rules/exports-valid.test.js
Expand Up @@ -84,6 +84,7 @@ describe('exports-valid Unit Tests', () => {
title: 'some error in nested conditions',
config: {conditions: ['node']},
input: {node: {foo: './a.js'}},
message: 'condition `foo` not in supported conditions `node,default`',
},
{
title: 'two valid values in fallback array',
Expand Down

0 comments on commit e612831

Please sign in to comment.