Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "eslint-plugin-tsdoc",
"comment": "Declare `eslint` as an optional peer dependency so published typings resolve against the consumer's ESLint under non-hoisted installers.",
"type": "patch"
}
],
"packageName": "eslint-plugin-tsdoc"
}
8 changes: 8 additions & 0 deletions eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
"@microsoft/tsdoc": "workspace:*",
"@typescript-eslint/utils": "~8.56.0"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
}
},
"peerDependencies": {
"eslint": ">=7"
},
Comment thread
iclanton marked this conversation as resolved.
"devDependencies": {
"tsdoc-build-rig": "workspace:*",
"@rushstack/heft": "1.2.7",
Expand Down
49 changes: 49 additions & 0 deletions eslint-plugin/src/tests/package.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import * as fs from 'node:fs';
import * as path from 'node:path';

interface IPackageJson {
dependencies?: { [name: string]: string };
peerDependencies?: { [name: string]: string };
}

function getPackageName(moduleSpecifier: string): string {
if (moduleSpecifier.startsWith('@')) {
return moduleSpecifier.split('/').slice(0, 2).join('/');
}
return moduleSpecifier.split('/')[0];
}

function collectImportedPackageNames(sourceText: string): string[] {
const importRegExp: RegExp = /(?:from|import)\s+['"]([^'"]+)['"]/g;
const packageNames: string[] = [];
for (const regExpMatch of sourceText.matchAll(importRegExp)) {
const moduleSpecifier: string | undefined = regExpMatch[1];
if (!moduleSpecifier || moduleSpecifier.startsWith('.') || moduleSpecifier.startsWith('node:')) {
continue;
}
packageNames.push(getPackageName(moduleSpecifier));
}
return packageNames;
}

test('published typings import only packages declared as dependencies or peerDependencies', () => {
const packageJsonPath: string = path.join(__dirname, '..', '..', 'package.json');
const packageJsonText: string = fs.readFileSync(packageJsonPath, { encoding: 'utf8' });
const packageJson: IPackageJson = JSON.parse(packageJsonText) as IPackageJson;

const declaredPackageNames: Set<string> = new Set<string>([
...Object.keys(packageJson.dependencies ?? {}),
...Object.keys(packageJson.peerDependencies ?? {})
]);

const typingsPath: string = path.join(__dirname, '..', 'index.d.ts');
const typingsText: string = fs.readFileSync(typingsPath, { encoding: 'utf8' });
const missingPackageNames: string[] = collectImportedPackageNames(typingsText).filter(
(packageName: string) => !declaredPackageNames.has(packageName)
);

expect(missingPackageNames).toEqual([]);
});
Loading