Comparing version 0.1.13 to 0.1.14
@@ -61,2 +61,6 @@ export type MessageType = 'suggestion' | 'warning' | 'error' | ||
| BaseMessage<'EXPORTS_TYPES_SHOULD_BE_FIRST'> | ||
| BaseMessage< | ||
'EXPORTS_MODULE_SHOULD_PRECEED_IMPORT_REQUIRE', | ||
{ conditions: string[] } | ||
> | ||
| BaseMessage<'EXPORTS_DEFAULT_SHOULD_BE_LAST'> | ||
@@ -63,0 +67,0 @@ | BaseMessage<'EXPORTS_MODULE_SHOULD_BE_ESM'> |
import { publint as _publint } from '../src/index.js' | ||
/** | ||
* @type {import('..').publint} | ||
* @type {import('../index.d.ts').publint} | ||
*/ | ||
@@ -6,0 +6,0 @@ export function publint(options) { |
@@ -1,2 +0,1 @@ | ||
import fs from 'node:fs/promises' | ||
import path from 'node:path' | ||
@@ -8,3 +7,3 @@ import packlist from 'npm-packlist' | ||
/** | ||
* @type {import('..').publint} | ||
* @type {import('../index.d.ts').publint} | ||
*/ | ||
@@ -11,0 +10,0 @@ export async function publint(options) { |
@@ -5,3 +5,3 @@ export { formatMessagePath, getPkgPathValue } from '../src/utils.js' | ||
* no-op. leave users to implement themselves. | ||
* @type {import('../utils').printMessage} | ||
* @type {import('../utils.d.ts').printMessage} | ||
*/ | ||
@@ -8,0 +8,0 @@ export function printMessage() { |
{ | ||
"name": "publint", | ||
"version": "0.1.13", | ||
"version": "0.1.14", | ||
"description": "Lint packaging errors", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -9,3 +9,5 @@ import { | ||
getPublishedField, | ||
objectHasKeyNested | ||
objectHasKeyNested, | ||
isFilePathLintable, | ||
isFileContentLintable | ||
} from './utils.js' | ||
@@ -17,3 +19,3 @@ | ||
* Currently only used if pkg has no `exports` | ||
* @typedef {Required<import('..').Options> & { | ||
* @typedef {Required<import('../index.d.ts').Options> & { | ||
* _packedFiles?: string[] | ||
@@ -25,6 +27,6 @@ * }} Options | ||
* @param {Options} options | ||
* @returns {Promise<import('..').Message[]>} | ||
* @returns {Promise<import('../index.js').Message[]>} | ||
*/ | ||
export async function publint({ pkgDir, vfs, level, strict, _packedFiles }) { | ||
/** @type {import('..').Message[]} */ | ||
/** @type {import('../index.d.ts').Message[]} */ | ||
const messages = [] | ||
@@ -45,13 +47,2 @@ /** | ||
/** | ||
* @param {string} filePath | ||
*/ | ||
function isPathLintable(filePath) { | ||
return ( | ||
filePath.endsWith('.js') || | ||
filePath.endsWith('.mjs') || | ||
filePath.endsWith('.cjs') | ||
) | ||
} | ||
/** | ||
* @param {string} path file path to read | ||
@@ -265,6 +256,7 @@ * @param {string[]} [pkgPath] current path that tries to read this file. | ||
for (const filePath of files) { | ||
if (!isPathLintable(filePath)) continue | ||
if (!isFilePathLintable(filePath)) continue | ||
pq.push(async () => { | ||
const fileContent = await readFile(filePath, []) | ||
if (fileContent === false) return | ||
if (!isFileContentLintable(fileContent)) return | ||
const actualFormat = getCodeFormat(fileContent) | ||
@@ -417,3 +409,3 @@ const expectFormat = await getFilePathFormat(filePath, vfs) | ||
// TODO: maybe check .ts in the future | ||
if (!isPathLintable(filePath)) continue | ||
if (!isFilePathLintable(filePath)) continue | ||
pq.push(async () => { | ||
@@ -423,2 +415,3 @@ // could fail if in !isGlob | ||
if (fileContent === false) return | ||
if (!isFileContentLintable(fileContent)) return | ||
// the `module` condition is only used by bundlers and must be ESM | ||
@@ -510,2 +503,28 @@ if (currentPath.includes('module')) { | ||
// a 'module' export should always preceed 'import' or 'require' | ||
if ('module' in exports) { | ||
const conditions = [] | ||
if ( | ||
'require' in exports && | ||
exportsKeys.indexOf('module') > exportsKeys.indexOf('require') | ||
) { | ||
conditions.push('require') | ||
} | ||
if ( | ||
'import' in exports && | ||
exportsKeys.indexOf('module') > exportsKeys.indexOf('import') | ||
) { | ||
conditions.push('import') | ||
} | ||
if (conditions.length > 0) { | ||
messages.push({ | ||
code: 'EXPORTS_MODULE_SHOULD_PRECEED_IMPORT_REQUIRE', | ||
args: { conditions }, | ||
path: currentPath.concat('module'), | ||
type: 'error' | ||
}) | ||
} | ||
} | ||
// the default export should be the last condition | ||
@@ -512,0 +531,0 @@ if ( |
@@ -5,4 +5,4 @@ import c from 'picocolors' | ||
/** | ||
* @param {import('..').Message} m | ||
* @param {import('./utils').Pkg} pkg | ||
* @param {import('../index.d.ts').Message} m | ||
* @param {import('./utils.js').Pkg} pkg | ||
*/ | ||
@@ -60,2 +60,12 @@ export function printMessage(m, pkg) { | ||
return `${c.bold(fp(m.path))} should be the first in the object as required by TypeScript.` | ||
case 'EXPORTS_MODULE_SHOULD_PRECEED_IMPORT_REQUIRE': { | ||
let conditions = `the ${m.args.conditions | ||
.map((cond) => `"${c.bold(cond)}"`) | ||
.join(' and ')} condition` | ||
if (m.args.conditions.length !== 1) { | ||
conditions += 's' | ||
} | ||
// prettier-ignore | ||
return `${c.bold(fp(m.path))} should come before ${conditions} so it can take precedence when used by a bundler.` | ||
} | ||
case 'EXPORTS_DEFAULT_SHOULD_BE_LAST': | ||
@@ -72,3 +82,3 @@ // prettier-ignore | ||
// prettier-ignore | ||
return `${c.bold('pkg.browser')} can be refactored to use ${c.bold('pkg.exports')} and the ${c.bold('browser')} condition instead to declare browser-specific exports. (This will be a breaking change)` | ||
return `${c.bold('pkg.browser')} can be refactored to use ${c.bold('pkg.exports')} and the ${c.bold('"browser"')} condition instead to declare browser-specific exports. (This will be a breaking change)` | ||
case 'TYPES_NOT_EXPORTED': { | ||
@@ -75,0 +85,0 @@ let target = fp(m.path) |
@@ -18,2 +18,5 @@ /** | ||
/([\s;]|^)(import[\w,{}\s*]*from|import\s*['"*{]|export\b\s*(?:[*{]|default|type|function|const|var|let|async function)|import\.meta\b)/m | ||
/** | ||
* @param {string} code | ||
*/ | ||
export function isCodeEsm(code) { | ||
@@ -26,2 +29,5 @@ return ESM_CONTENT_RE.test(code) | ||
/([\s;]|^)(module.exports\b|exports\.\w|require\s*\(|global\.\w|Object\.(defineProperty|defineProperties|assign)\s*\(\s*exports\b)/m | ||
/** | ||
* @param {string} code | ||
*/ | ||
export function isCodeCjs(code) { | ||
@@ -33,2 +39,5 @@ return CJS_CONTENT_RE.test(code) | ||
const SINGLELINE_COMMENTS_RE = /\/\/.*/g | ||
/** | ||
* @param {string} code | ||
*/ | ||
export function stripComments(code) { | ||
@@ -67,3 +76,3 @@ return code | ||
* @param {string} globStr An absolute glob string that must contain one `*` | ||
* @param {import('..').Vfs} vfs | ||
* @param {import('../index.d.ts').Vfs} vfs | ||
* @param {string[]} [packedFiles] | ||
@@ -126,3 +135,6 @@ * @returns {Promise<string[]>} Matched file paths | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping | ||
/** | ||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping | ||
* @param {string} string | ||
*/ | ||
function escapeRegExp(string) { | ||
@@ -141,3 +153,3 @@ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') | ||
* @param {string} filePath | ||
* @param {import('..').Vfs} vfs | ||
* @param {import('../index.d.ts').Vfs} vfs | ||
* @returns {Promise<CodeFormat>} | ||
@@ -180,5 +192,32 @@ */ | ||
/** | ||
* | ||
* only lint JS files. TS and others not supported. | ||
* @param {string} filePath | ||
* @param {import('..').Vfs} vfs | ||
*/ | ||
export function isFilePathLintable(filePath) { | ||
return ( | ||
filePath.endsWith('.js') || | ||
filePath.endsWith('.mjs') || | ||
filePath.endsWith('.cjs') | ||
) | ||
} | ||
// support: | ||
// // @flow | ||
// /* @flow */ | ||
// /** @flow */ | ||
// /** | ||
// * @flow | ||
// */ | ||
const FLOW_COMMENT_RE = /^\s*(?:\/\/|\/\*\*?|\*)\s*@flow/m | ||
/** | ||
* don't lint Flow files, which is annotated by an initial `@flow` comment | ||
* @param {string} fileContent | ||
*/ | ||
export function isFileContentLintable(fileContent) { | ||
return !FLOW_COMMENT_RE.test(fileContent) | ||
} | ||
/** | ||
* @param {string} filePath | ||
* @param {import('../index.d.ts').Vfs} vfs | ||
* @returns {Promise<Pkg | undefined>} | ||
@@ -210,4 +249,7 @@ */ | ||
for (const part of path) { | ||
// is invalid js var name | ||
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(part)) { | ||
if (/^\d+$/.test(part)) { | ||
// plain number | ||
formatted += `[${part}]` | ||
} else if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(part)) { | ||
// is invalid js var name | ||
formatted += `["${part}"]` | ||
@@ -214,0 +256,0 @@ } else { |
@@ -7,3 +7,3 @@ import fs from 'node:fs' | ||
* Creates a node-compatible Vfs object | ||
* @returns {import('..').Vfs} | ||
* @returns {import('../index.d.ts').Vfs} | ||
*/ | ||
@@ -10,0 +10,0 @@ export function createNodeVfs() { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
48744
1318