@rushstack/eslint-plugin-packlets 0.3.5 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -192,7 +192,7 @@ module.exports = {
192
192
  ## Links
193
193
 
194
194
  - [CHANGELOG.md](
195
- https://github.com/microsoft/rushstack/blob/master/stack/eslint-plugin-packlets/CHANGELOG.md) - Find
195
+ https://github.com/microsoft/rushstack/blob/main/stack/eslint-plugin-packlets/CHANGELOG.md) - Find
196
196
  out what's new in the latest version
197
197
  - [@rushstack/eslint-config](https://www.npmjs.com/package/@rushstack/eslint-config) documentation
198
198
 
@@ -3,7 +3,11 @@
3
3
  // See LICENSE in the project root for license information.
4
4
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
5
  if (k2 === undefined) k2 = k;
6
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
7
11
  }) : (function(o, m, k, k2) {
8
12
  if (k2 === undefined) k2 = k;
9
13
  o[k2] = m[k];
@@ -1 +1 @@
1
- {"version":3,"file":"PackletAnalyzer.js","sourceRoot":"","sources":["../src/PackletAnalyzer.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as fs from 'fs';\nimport { Path } from './Path';\n\nexport type InputFileMessageIds =\n | 'file-in-packets-folder'\n | 'invalid-packlet-name'\n | 'misplaced-packlets-folder'\n | 'missing-src-folder'\n | 'missing-tsconfig'\n | 'packlet-folder-case';\n\nexport type ImportMessageIds =\n | 'bypassed-entry-point'\n | 'circular-entry-point'\n | 'packlet-importing-project-file';\n\nexport interface IAnalyzerError {\n messageId: InputFileMessageIds | ImportMessageIds;\n data?: Readonly<Record<string, unknown>>;\n}\n\nexport class PackletAnalyzer {\n private static _validPackletName: RegExp = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\n /**\n * The input file being linted.\n *\n * Example: \"/path/to/my-project/src/file.ts\"\n */\n public readonly inputFilePath: string;\n\n /**\n * An error that occurred while analyzing the inputFilePath.\n */\n public readonly error: IAnalyzerError | undefined;\n\n /**\n * Returned to indicate that the linter can ignore this file. Possible reasons:\n * - It's outside the \"src\" folder\n * - The project doesn't define any packlets\n */\n public readonly nothingToDo: boolean;\n\n /**\n * If true, then the \"src/packlets\" folder exists.\n */\n public readonly projectUsesPacklets: boolean;\n\n /**\n * The absolute path of the \"src/packlets\" folder.\n */\n public readonly packletsFolderPath: string | undefined;\n\n /**\n * The packlet that the inputFilePath is under, if any.\n */\n public readonly inputFilePackletName: string | undefined;\n\n /**\n * Returns true if inputFilePath belongs to a packlet and is the entry point index.ts.\n */\n public readonly isEntryPoint: boolean;\n\n private constructor(inputFilePath: string, tsconfigFilePath: string | undefined) {\n this.inputFilePath = inputFilePath;\n this.error = undefined;\n this.nothingToDo = false;\n this.projectUsesPacklets = false;\n this.packletsFolderPath = undefined;\n this.inputFilePackletName = undefined;\n this.isEntryPoint = false;\n\n // Example: /path/to/my-project/src\n let srcFolderPath: string | undefined;\n\n if (!tsconfigFilePath) {\n this.error = { messageId: 'missing-tsconfig' };\n return;\n }\n\n srcFolderPath = Path.join(Path.dirname(tsconfigFilePath), 'src');\n\n if (!fs.existsSync(srcFolderPath)) {\n this.error = { messageId: 'missing-src-folder', data: { srcFolderPath } };\n return;\n }\n\n if (!Path.isUnder(inputFilePath, srcFolderPath)) {\n // Ignore files outside the \"src\" folder\n this.nothingToDo = true;\n return;\n }\n\n // Example: packlets/my-packlet/index.ts\n const inputFilePathRelativeToSrc: string = Path.relative(srcFolderPath, inputFilePath);\n\n // Example: [ 'packlets', 'my-packlet', 'index.ts' ]\n const pathParts: string[] = inputFilePathRelativeToSrc.split(/[\\/\\\\]+/);\n\n let underPackletsFolder: boolean = false;\n\n const expectedPackletsFolder: string = Path.join(srcFolderPath, 'packlets');\n\n for (let i = 0; i < pathParts.length; ++i) {\n const pathPart: string = pathParts[i];\n if (pathPart.toUpperCase() === 'PACKLETS') {\n if (pathPart !== 'packlets') {\n // Example: /path/to/my-project/src/PACKLETS\n const packletsFolderPath: string = Path.join(srcFolderPath, ...pathParts.slice(0, i + 1));\n this.error = { messageId: 'packlet-folder-case', data: { packletsFolderPath } };\n return;\n }\n\n if (i !== 0) {\n this.error = { messageId: 'misplaced-packlets-folder', data: { expectedPackletsFolder } };\n return;\n }\n\n underPackletsFolder = true;\n }\n }\n\n if (underPackletsFolder || fs.existsSync(expectedPackletsFolder)) {\n // packletsAbsolutePath\n this.projectUsesPacklets = true;\n this.packletsFolderPath = expectedPackletsFolder;\n }\n\n if (underPackletsFolder) {\n if (pathParts.length === 2) {\n // Example: src/packlets/SomeFile.ts\n this.error = { messageId: 'file-in-packets-folder' };\n return;\n }\n if (pathParts.length >= 2) {\n // Example: 'my-packlet'\n const packletName: string = pathParts[1];\n this.inputFilePackletName = packletName;\n\n if (pathParts.length === 3) {\n // Example: 'index.ts' or 'index.tsx'\n const thirdPart: string = pathParts[2];\n\n // Example: 'index'\n const thirdPartWithoutExtension: string = Path.parse(thirdPart).name;\n\n if (thirdPartWithoutExtension.toUpperCase() === 'INDEX') {\n if (!PackletAnalyzer._validPackletName.test(packletName)) {\n this.error = { messageId: 'invalid-packlet-name', data: { packletName } };\n return;\n }\n\n this.isEntryPoint = true;\n }\n }\n }\n }\n\n if (this.error === undefined && !this.projectUsesPacklets) {\n this.nothingToDo = true;\n }\n }\n\n public static analyzeInputFile(inputFilePath: string, tsconfigFilePath: string | undefined) {\n return new PackletAnalyzer(inputFilePath, tsconfigFilePath);\n }\n\n public analyzeImport(modulePath: string): IAnalyzerError | undefined {\n if (!this.packletsFolderPath) {\n // The caller should ensure this can never happen\n throw new Error('Internal error: packletsFolderPath is not defined');\n }\n\n // Example: /path/to/my-project/src/packlets/my-packlet\n const inputFileFolder: string = Path.dirname(this.inputFilePath);\n\n // Example: /path/to/my-project/src/other-packlet/index\n const importedPath: string = Path.resolve(inputFileFolder, modulePath);\n\n // Is the imported path referring to a file under the src/packlets folder?\n if (Path.isUnder(importedPath, this.packletsFolderPath)) {\n // Example: other-packlet/index\n const importedPathRelativeToPackletsFolder: string = Path.relative(\n this.packletsFolderPath,\n importedPath\n );\n // Example: [ 'other-packlet', 'index' ]\n const importedPathParts: string[] = importedPathRelativeToPackletsFolder.split(/[\\/\\\\]+/);\n if (importedPathParts.length > 0) {\n // Example: 'other-packlet'\n const importedPackletName: string = importedPathParts[0];\n\n // We are importing from a packlet. Is the input file part of the same packlet?\n if (this.inputFilePackletName && importedPackletName === this.inputFilePackletName) {\n // Yes. Then our import must NOT use the packlet entry point.\n\n // Example: 'index'\n //\n // We discard the file extension to handle a degenerate case like:\n // import { X } from \"../index.js\";\n const lastPart: string = Path.parse(importedPathParts[importedPathParts.length - 1]).name;\n let pathToCompare: string;\n if (lastPart.toUpperCase() === 'INDEX') {\n // Example:\n // importedPath = /path/to/my-project/src/other-packlet/index\n // pathToCompare = /path/to/my-project/src/other-packlet\n pathToCompare = Path.dirname(importedPath);\n } else {\n pathToCompare = importedPath;\n }\n\n // Example: /path/to/my-project/src/other-packlet\n const entryPointPath: string = Path.join(this.packletsFolderPath, importedPackletName);\n\n if (Path.isEqual(pathToCompare, entryPointPath)) {\n return {\n messageId: 'circular-entry-point'\n };\n }\n } else {\n // No. If we are not part of the same packlet, then the module path must refer\n // to the index.ts entry point.\n\n // Example: /path/to/my-project/src/other-packlet\n const entryPointPath: string = Path.join(this.packletsFolderPath, importedPackletName);\n\n if (!Path.isEqual(importedPath, entryPointPath)) {\n // Example: \"../packlets/other-packlet\"\n const entryPointModulePath: string = Path.convertToSlashes(\n Path.relative(inputFileFolder, entryPointPath)\n );\n\n return {\n messageId: 'bypassed-entry-point',\n data: { entryPointModulePath }\n };\n }\n }\n }\n } else {\n // The imported path does NOT refer to a file under the src/packlets folder\n if (this.inputFilePackletName) {\n return {\n messageId: 'packlet-importing-project-file'\n };\n }\n }\n\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"PackletAnalyzer.js","sourceRoot":"","sources":["../src/PackletAnalyzer.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as fs from 'fs';\nimport { Path } from './Path';\n\nexport type InputFileMessageIds =\n | 'file-in-packets-folder'\n | 'invalid-packlet-name'\n | 'misplaced-packlets-folder'\n | 'missing-src-folder'\n | 'missing-tsconfig'\n | 'packlet-folder-case';\n\nexport type ImportMessageIds =\n | 'bypassed-entry-point'\n | 'circular-entry-point'\n | 'packlet-importing-project-file';\n\nexport interface IAnalyzerError {\n messageId: InputFileMessageIds | ImportMessageIds;\n data?: Readonly<Record<string, unknown>>;\n}\n\nexport class PackletAnalyzer {\n private static _validPackletName: RegExp = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\n /**\n * The input file being linted.\n *\n * Example: \"/path/to/my-project/src/file.ts\"\n */\n public readonly inputFilePath: string;\n\n /**\n * An error that occurred while analyzing the inputFilePath.\n */\n public readonly error: IAnalyzerError | undefined;\n\n /**\n * Returned to indicate that the linter can ignore this file. Possible reasons:\n * - It's outside the \"src\" folder\n * - The project doesn't define any packlets\n */\n public readonly nothingToDo: boolean;\n\n /**\n * If true, then the \"src/packlets\" folder exists.\n */\n public readonly projectUsesPacklets: boolean;\n\n /**\n * The absolute path of the \"src/packlets\" folder.\n */\n public readonly packletsFolderPath: string | undefined;\n\n /**\n * The packlet that the inputFilePath is under, if any.\n */\n public readonly inputFilePackletName: string | undefined;\n\n /**\n * Returns true if inputFilePath belongs to a packlet and is the entry point index.ts.\n */\n public readonly isEntryPoint: boolean;\n\n private constructor(inputFilePath: string, tsconfigFilePath: string | undefined) {\n this.inputFilePath = inputFilePath;\n this.error = undefined;\n this.nothingToDo = false;\n this.projectUsesPacklets = false;\n this.packletsFolderPath = undefined;\n this.inputFilePackletName = undefined;\n this.isEntryPoint = false;\n\n // Example: /path/to/my-project/src\n let srcFolderPath: string | undefined;\n\n if (!tsconfigFilePath) {\n this.error = { messageId: 'missing-tsconfig' };\n return;\n }\n\n srcFolderPath = Path.join(Path.dirname(tsconfigFilePath), 'src');\n\n if (!fs.existsSync(srcFolderPath)) {\n this.error = { messageId: 'missing-src-folder', data: { srcFolderPath } };\n return;\n }\n\n if (!Path.isUnder(inputFilePath, srcFolderPath)) {\n // Ignore files outside the \"src\" folder\n this.nothingToDo = true;\n return;\n }\n\n // Example: packlets/my-packlet/index.ts\n const inputFilePathRelativeToSrc: string = Path.relative(srcFolderPath, inputFilePath);\n\n // Example: [ 'packlets', 'my-packlet', 'index.ts' ]\n const pathParts: string[] = inputFilePathRelativeToSrc.split(/[\\/\\\\]+/);\n\n let underPackletsFolder: boolean = false;\n\n const expectedPackletsFolder: string = Path.join(srcFolderPath, 'packlets');\n\n for (let i = 0; i < pathParts.length; ++i) {\n const pathPart: string = pathParts[i];\n if (pathPart.toUpperCase() === 'PACKLETS') {\n if (pathPart !== 'packlets') {\n // Example: /path/to/my-project/src/PACKLETS\n const packletsFolderPath: string = Path.join(srcFolderPath, ...pathParts.slice(0, i + 1));\n this.error = { messageId: 'packlet-folder-case', data: { packletsFolderPath } };\n return;\n }\n\n if (i !== 0) {\n this.error = { messageId: 'misplaced-packlets-folder', data: { expectedPackletsFolder } };\n return;\n }\n\n underPackletsFolder = true;\n }\n }\n\n if (underPackletsFolder || fs.existsSync(expectedPackletsFolder)) {\n // packletsAbsolutePath\n this.projectUsesPacklets = true;\n this.packletsFolderPath = expectedPackletsFolder;\n }\n\n if (underPackletsFolder) {\n if (pathParts.length === 2) {\n // Example: src/packlets/SomeFile.ts\n this.error = { messageId: 'file-in-packets-folder' };\n return;\n }\n if (pathParts.length >= 2) {\n // Example: 'my-packlet'\n const packletName: string = pathParts[1];\n this.inputFilePackletName = packletName;\n\n if (pathParts.length === 3) {\n // Example: 'index.ts' or 'index.tsx'\n const thirdPart: string = pathParts[2];\n\n // Example: 'index'\n const thirdPartWithoutExtension: string = Path.parse(thirdPart).name;\n\n if (thirdPartWithoutExtension.toUpperCase() === 'INDEX') {\n if (!PackletAnalyzer._validPackletName.test(packletName)) {\n this.error = { messageId: 'invalid-packlet-name', data: { packletName } };\n return;\n }\n\n this.isEntryPoint = true;\n }\n }\n }\n }\n\n if (this.error === undefined && !this.projectUsesPacklets) {\n this.nothingToDo = true;\n }\n }\n\n public static analyzeInputFile(inputFilePath: string, tsconfigFilePath: string | undefined) {\n return new PackletAnalyzer(inputFilePath, tsconfigFilePath);\n }\n\n public analyzeImport(modulePath: string): IAnalyzerError | undefined {\n if (!this.packletsFolderPath) {\n // The caller should ensure this can never happen\n throw new Error('Internal error: packletsFolderPath is not defined');\n }\n\n // Example: /path/to/my-project/src/packlets/my-packlet\n const inputFileFolder: string = Path.dirname(this.inputFilePath);\n\n // Example: /path/to/my-project/src/other-packlet/index\n const importedPath: string = Path.resolve(inputFileFolder, modulePath);\n\n // Is the imported path referring to a file under the src/packlets folder?\n if (Path.isUnder(importedPath, this.packletsFolderPath)) {\n // Example: other-packlet/index\n const importedPathRelativeToPackletsFolder: string = Path.relative(\n this.packletsFolderPath,\n importedPath\n );\n // Example: [ 'other-packlet', 'index' ]\n const importedPathParts: string[] = importedPathRelativeToPackletsFolder.split(/[\\/\\\\]+/);\n if (importedPathParts.length > 0) {\n // Example: 'other-packlet'\n const importedPackletName: string = importedPathParts[0];\n\n // We are importing from a packlet. Is the input file part of the same packlet?\n if (this.inputFilePackletName && importedPackletName === this.inputFilePackletName) {\n // Yes. Then our import must NOT use the packlet entry point.\n\n // Example: 'index'\n //\n // We discard the file extension to handle a degenerate case like:\n // import { X } from \"../index.js\";\n const lastPart: string = Path.parse(importedPathParts[importedPathParts.length - 1]).name;\n let pathToCompare: string;\n if (lastPart.toUpperCase() === 'INDEX') {\n // Example:\n // importedPath = /path/to/my-project/src/other-packlet/index\n // pathToCompare = /path/to/my-project/src/other-packlet\n pathToCompare = Path.dirname(importedPath);\n } else {\n pathToCompare = importedPath;\n }\n\n // Example: /path/to/my-project/src/other-packlet\n const entryPointPath: string = Path.join(this.packletsFolderPath, importedPackletName);\n\n if (Path.isEqual(pathToCompare, entryPointPath)) {\n return {\n messageId: 'circular-entry-point'\n };\n }\n } else {\n // No. If we are not part of the same packlet, then the module path must refer\n // to the index.ts entry point.\n\n // Example: /path/to/my-project/src/other-packlet\n const entryPointPath: string = Path.join(this.packletsFolderPath, importedPackletName);\n\n if (!Path.isEqual(importedPath, entryPointPath)) {\n // Example: \"../packlets/other-packlet\"\n const entryPointModulePath: string = Path.convertToSlashes(\n Path.relative(inputFileFolder, entryPointPath)\n );\n\n return {\n messageId: 'bypassed-entry-point',\n data: { entryPointModulePath }\n };\n }\n }\n }\n } else {\n // The imported path does NOT refer to a file under the src/packlets folder\n if (this.inputFilePackletName) {\n return {\n messageId: 'packlet-importing-project-file'\n };\n }\n }\n\n return undefined;\n }\n}\n"]}
package/lib/Path.js CHANGED
@@ -3,7 +3,11 @@
3
3
  // See LICENSE in the project root for license information.
4
4
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
5
  if (k2 === undefined) k2 = k;
6
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
7
11
  }) : (function(o, m, k, k2) {
8
12
  if (k2 === undefined) k2 = k;
9
13
  o[k2] = m[k];
package/lib/Path.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Path.js","sourceRoot":"","sources":["../src/Path.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as path from 'path';\nimport * as fs from 'fs';\n\nexport type ParsedPath = path.ParsedPath;\n\nexport class Path {\n /**\n * Whether the filesystem is assumed to be case sensitive for Path operations.\n *\n * @remarks\n * Regardless of operating system, a given file system's paths may be case-sensitive or case-insensitive.\n * If a volume is mounted under a subfolder, then different parts of a path can even have different\n * case-sensitivity. The Node.js \"path\" API naively assumes that all Windows paths are case-insensitive,\n * and that all other OS's are case-sensitive. This is way off, for example a modern MacBook has a\n * case-insensitive filesystem by default. There isn't an easy workaround because Node.js does not expose\n * the native OS APIs that would give accurate answers.\n *\n * The TypeScript compiler does somewhat better: it performs an empirical test of its own bundle path to see\n * whether it can be read using different case. If so, it normalizes all paths to lowercase (sometimes with\n * no API for retrieving the real path). This caused our Path.isUnder() to return incorrect answers because\n * it relies on Node.js path.relative().\n *\n * To solve that problem, Path.ts performs an empirical test similar to what the TypeScript compiler does,\n * and then we adjust path.relative() to be case insensitive if appropriate.\n *\n * @see {@link https://nodejs.org/en/docs/guides/working-with-different-filesystems/}\n */\n public static usingCaseSensitive: boolean = Path._detectCaseSensitive();\n\n private static _detectCaseSensitive(): boolean {\n // Can our own file be accessed using a path with different case? If so, then the filesystem is case-insensitive.\n return !fs.existsSync(__filename.toUpperCase());\n }\n\n // Removes redundant trailing slashes from a path.\n private static _trimTrailingSlashes(inputPath: string): string {\n // Examples:\n // \"/a/b///\\\\\" --> \"/a/b\"\n // \"/\" --> \"/\"\n return inputPath.replace(/(?<=[^\\/\\\\])[\\/\\\\]+$/, '');\n }\n\n // An implementation of path.relative() that is case-insensitive.\n private static _relativeCaseInsensitive(from: string, to: string): string {\n // path.relative() apples path.normalize() and also trims any trailing slashes.\n // Since we'll be matching toNormalized against result, we need to do that for our string as well.\n const normalizedTo: string = Path._trimTrailingSlashes(path.normalize(to));\n\n // We start by converting everything to uppercase and call path.relative()\n const uppercasedFrom: string = from.toUpperCase();\n const uppercasedTo: string = normalizedTo.toUpperCase();\n\n // The result will be all uppercase because its inputs were uppercased\n const uppercasedResult: string = path.relative(uppercasedFrom, uppercasedTo);\n\n // Are there any cased characters in the result?\n if (uppercasedResult.toLowerCase() === uppercasedResult) {\n // No cased characters\n // Example: \"../..\"\n return uppercasedResult;\n }\n\n // Example:\n // from=\"/a/b/c\"\n // to=\"/a/b/d/e\"\n //\n // fromNormalized=\"/A/B/C\"\n // toNormalized=\"/A/B/D/E\"\n //\n // result=\"../D/E\"\n //\n // Scan backwards comparing uppercasedResult versus uppercasedTo, stopping at the first place where they differ.\n let resultIndex: number = uppercasedResult.length;\n let toIndex: number = normalizedTo.length;\n for (;;) {\n if (resultIndex === 0 || toIndex === 0) {\n // Stop if we reach the start of the string\n break;\n }\n\n if (uppercasedResult.charCodeAt(resultIndex - 1) !== uppercasedTo.charCodeAt(toIndex - 1)) {\n // Stop before we reach a character that is different\n break;\n }\n\n --resultIndex;\n --toIndex;\n }\n\n // Replace the matching part with the properly cased substring from the \"normalizedTo\" input\n //\n // Example:\n // \"..\" + \"/d/e\" = \"../d/e\"\n return uppercasedResult.substring(0, resultIndex) + normalizedTo.substring(toIndex);\n }\n\n public static relative(from: string, to: string): string {\n if (!Path.usingCaseSensitive) {\n return Path._relativeCaseInsensitive(from, to);\n }\n return path.relative(from, to);\n }\n\n // --------------------------------------------------------------------------------------------------------\n // The operations below don't care about case sensitivity\n\n public static dirname(p: string): string {\n return path.dirname(p);\n }\n\n public static join(...paths: string[]): string {\n return path.join(...paths);\n }\n\n public static resolve(...pathSegments: string[]): string {\n return path.resolve(...pathSegments);\n }\n\n public static parse(pathString: string): ParsedPath {\n return path.parse(pathString);\n }\n\n // --------------------------------------------------------------------------------------------------------\n // The operations below are borrowed from @rushstack/node-core-library\n\n private static _relativePathRegex: RegExp = /^[.\\/\\\\]+$/;\n\n /**\n * Returns true if \"childPath\" is located inside the \"parentFolderPath\" folder\n * or one of its child folders. Note that \"parentFolderPath\" is not considered to be\n * under itself. The \"childPath\" can refer to any type of file system object.\n *\n * @remarks\n * The indicated file/folder objects are not required to actually exist on disk.\n * For example, \"parentFolderPath\" is interpreted as a folder name even if it refers to a file.\n * If the paths are relative, they will first be resolved using path.resolve().\n */\n public static isUnder(childPath: string, parentFolderPath: string): boolean {\n const relativePath: string = Path.relative(childPath, parentFolderPath);\n return Path._relativePathRegex.test(relativePath);\n }\n\n /**\n * Returns true if `path1` and `path2` refer to the same underlying path.\n *\n * @remarks\n *\n * The comparison is performed using `path.relative()`.\n */\n public static isEqual(path1: string, path2: string): boolean {\n return Path.relative(path1, path2) === '';\n }\n\n /**\n * Replaces Windows-style backslashes with POSIX-style slashes.\n *\n * @remarks\n * POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.\n */\n public static convertToSlashes(inputPath: string): string {\n return inputPath.split('\\\\').join('/');\n }\n}\n"]}
1
+ {"version":3,"file":"Path.js","sourceRoot":"","sources":["../src/Path.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;;;;;;;;;;;;;;;;;;;;;;;;AAE3D,2CAA6B;AAC7B,uCAAyB;AAIzB,MAAa,IAAI;IAwBP,MAAM,CAAC,oBAAoB;QACjC,kHAAkH;QAClH,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,kDAAkD;IAC1C,MAAM,CAAC,oBAAoB,CAAC,SAAiB;QACnD,YAAY;QACZ,yBAAyB;QACzB,sBAAsB;QACtB,OAAO,SAAS,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,iEAAiE;IACzD,MAAM,CAAC,wBAAwB,CAAC,IAAY,EAAE,EAAU;QAC9D,+EAA+E;QAC/E,kGAAkG;QAClG,MAAM,YAAY,GAAW,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;QAE3E,0EAA0E;QAC1E,MAAM,cAAc,GAAW,IAAI,CAAC,WAAW,EAAE,CAAC;QAClD,MAAM,YAAY,GAAW,YAAY,CAAC,WAAW,EAAE,CAAC;QAExD,sEAAsE;QACtE,MAAM,gBAAgB,GAAW,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;QAE7E,gDAAgD;QAChD,IAAI,gBAAgB,CAAC,WAAW,EAAE,KAAK,gBAAgB,EAAE;YACvD,sBAAsB;YACtB,mBAAmB;YACnB,OAAO,gBAAgB,CAAC;SACzB;QAED,WAAW;QACX,kBAAkB;QAClB,kBAAkB;QAClB,EAAE;QACF,4BAA4B;QAC5B,4BAA4B;QAC5B,EAAE;QACF,oBAAoB;QACpB,EAAE;QACF,gHAAgH;QAChH,IAAI,WAAW,GAAW,gBAAgB,CAAC,MAAM,CAAC;QAClD,IAAI,OAAO,GAAW,YAAY,CAAC,MAAM,CAAC;QAC1C,SAAS;YACP,IAAI,WAAW,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,EAAE;gBACtC,2CAA2C;gBAC3C,MAAM;aACP;YAED,IAAI,gBAAgB,CAAC,UAAU,CAAC,WAAW,GAAG,CAAC,CAAC,KAAK,YAAY,CAAC,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE;gBACzF,qDAAqD;gBACrD,MAAM;aACP;YAED,EAAE,WAAW,CAAC;YACd,EAAE,OAAO,CAAC;SACX;QAED,4FAA4F;QAC5F,EAAE;QACF,WAAW;QACX,6BAA6B;QAC7B,OAAO,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,WAAW,CAAC,GAAG,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACtF,CAAC;IAEM,MAAM,CAAC,QAAQ,CAAC,IAAY,EAAE,EAAU;QAC7C,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE;YAC5B,OAAO,IAAI,CAAC,wBAAwB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;SAChD;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,2GAA2G;IAC3G,yDAAyD;IAElD,MAAM,CAAC,OAAO,CAAC,CAAS;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAEM,MAAM,CAAC,IAAI,CAAC,GAAG,KAAe;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;IAC7B,CAAC;IAEM,MAAM,CAAC,OAAO,CAAC,GAAG,YAAsB;QAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC;IACvC,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,UAAkB;QACpC,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;IAOD;;;;;;;;;OASG;IACI,MAAM,CAAC,OAAO,CAAC,SAAiB,EAAE,gBAAwB;QAC/D,MAAM,YAAY,GAAW,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;QACxE,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,OAAO,CAAC,KAAa,EAAE,KAAa;QAChD,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,gBAAgB,CAAC,SAAiB;QAC9C,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;;AA5JH,oBA6JC;AA5JC;;;;;;;;;;;;;;;;;;;;GAoBG;AACW,uBAAkB,GAAY,IAAI,CAAC,oBAAoB,EAAE,CAAC;AA+FxE,2GAA2G;AAC3G,sEAAsE;AAEvD,uBAAkB,GAAW,YAAY,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as path from 'path';\nimport * as fs from 'fs';\n\nexport type ParsedPath = path.ParsedPath;\n\nexport class Path {\n /**\n * Whether the filesystem is assumed to be case sensitive for Path operations.\n *\n * @remarks\n * Regardless of operating system, a given file system's paths may be case-sensitive or case-insensitive.\n * If a volume is mounted under a subfolder, then different parts of a path can even have different\n * case-sensitivity. The Node.js \"path\" API naively assumes that all Windows paths are case-insensitive,\n * and that all other OS's are case-sensitive. This is way off, for example a modern MacBook has a\n * case-insensitive filesystem by default. There isn't an easy workaround because Node.js does not expose\n * the native OS APIs that would give accurate answers.\n *\n * The TypeScript compiler does somewhat better: it performs an empirical test of its own bundle path to see\n * whether it can be read using different case. If so, it normalizes all paths to lowercase (sometimes with\n * no API for retrieving the real path). This caused our Path.isUnder() to return incorrect answers because\n * it relies on Node.js path.relative().\n *\n * To solve that problem, Path.ts performs an empirical test similar to what the TypeScript compiler does,\n * and then we adjust path.relative() to be case insensitive if appropriate.\n *\n * @see {@link https://nodejs.org/en/docs/guides/working-with-different-filesystems/}\n */\n public static usingCaseSensitive: boolean = Path._detectCaseSensitive();\n\n private static _detectCaseSensitive(): boolean {\n // Can our own file be accessed using a path with different case? If so, then the filesystem is case-insensitive.\n return !fs.existsSync(__filename.toUpperCase());\n }\n\n // Removes redundant trailing slashes from a path.\n private static _trimTrailingSlashes(inputPath: string): string {\n // Examples:\n // \"/a/b///\\\\\" --> \"/a/b\"\n // \"/\" --> \"/\"\n return inputPath.replace(/(?<=[^\\/\\\\])[\\/\\\\]+$/, '');\n }\n\n // An implementation of path.relative() that is case-insensitive.\n private static _relativeCaseInsensitive(from: string, to: string): string {\n // path.relative() apples path.normalize() and also trims any trailing slashes.\n // Since we'll be matching toNormalized against result, we need to do that for our string as well.\n const normalizedTo: string = Path._trimTrailingSlashes(path.normalize(to));\n\n // We start by converting everything to uppercase and call path.relative()\n const uppercasedFrom: string = from.toUpperCase();\n const uppercasedTo: string = normalizedTo.toUpperCase();\n\n // The result will be all uppercase because its inputs were uppercased\n const uppercasedResult: string = path.relative(uppercasedFrom, uppercasedTo);\n\n // Are there any cased characters in the result?\n if (uppercasedResult.toLowerCase() === uppercasedResult) {\n // No cased characters\n // Example: \"../..\"\n return uppercasedResult;\n }\n\n // Example:\n // from=\"/a/b/c\"\n // to=\"/a/b/d/e\"\n //\n // fromNormalized=\"/A/B/C\"\n // toNormalized=\"/A/B/D/E\"\n //\n // result=\"../D/E\"\n //\n // Scan backwards comparing uppercasedResult versus uppercasedTo, stopping at the first place where they differ.\n let resultIndex: number = uppercasedResult.length;\n let toIndex: number = normalizedTo.length;\n for (;;) {\n if (resultIndex === 0 || toIndex === 0) {\n // Stop if we reach the start of the string\n break;\n }\n\n if (uppercasedResult.charCodeAt(resultIndex - 1) !== uppercasedTo.charCodeAt(toIndex - 1)) {\n // Stop before we reach a character that is different\n break;\n }\n\n --resultIndex;\n --toIndex;\n }\n\n // Replace the matching part with the properly cased substring from the \"normalizedTo\" input\n //\n // Example:\n // \"..\" + \"/d/e\" = \"../d/e\"\n return uppercasedResult.substring(0, resultIndex) + normalizedTo.substring(toIndex);\n }\n\n public static relative(from: string, to: string): string {\n if (!Path.usingCaseSensitive) {\n return Path._relativeCaseInsensitive(from, to);\n }\n return path.relative(from, to);\n }\n\n // --------------------------------------------------------------------------------------------------------\n // The operations below don't care about case sensitivity\n\n public static dirname(p: string): string {\n return path.dirname(p);\n }\n\n public static join(...paths: string[]): string {\n return path.join(...paths);\n }\n\n public static resolve(...pathSegments: string[]): string {\n return path.resolve(...pathSegments);\n }\n\n public static parse(pathString: string): ParsedPath {\n return path.parse(pathString);\n }\n\n // --------------------------------------------------------------------------------------------------------\n // The operations below are borrowed from @rushstack/node-core-library\n\n private static _relativePathRegex: RegExp = /^[.\\/\\\\]+$/;\n\n /**\n * Returns true if \"childPath\" is located inside the \"parentFolderPath\" folder\n * or one of its child folders. Note that \"parentFolderPath\" is not considered to be\n * under itself. The \"childPath\" can refer to any type of file system object.\n *\n * @remarks\n * The indicated file/folder objects are not required to actually exist on disk.\n * For example, \"parentFolderPath\" is interpreted as a folder name even if it refers to a file.\n * If the paths are relative, they will first be resolved using path.resolve().\n */\n public static isUnder(childPath: string, parentFolderPath: string): boolean {\n const relativePath: string = Path.relative(childPath, parentFolderPath);\n return Path._relativePathRegex.test(relativePath);\n }\n\n /**\n * Returns true if `path1` and `path2` refer to the same underlying path.\n *\n * @remarks\n *\n * The comparison is performed using `path.relative()`.\n */\n public static isEqual(path1: string, path2: string): boolean {\n return Path.relative(path1, path2) === '';\n }\n\n /**\n * Replaces Windows-style backslashes with POSIX-style slashes.\n *\n * @remarks\n * POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.\n */\n public static convertToSlashes(inputPath: string): string {\n return inputPath.split('\\\\').join('/');\n }\n}\n"]}
package/lib/readme.js CHANGED
@@ -3,7 +3,11 @@
3
3
  // See LICENSE in the project root for license information.
4
4
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
5
  if (k2 === undefined) k2 = k;
6
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
7
11
  }) : (function(o, m, k, k2) {
8
12
  if (k2 === undefined) k2 = k;
9
13
  o[k2] = m[k];
package/lib/readme.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"readme.js","sourceRoot":"","sources":["../src/readme.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport type { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';\nimport { ESLintUtils } from '@typescript-eslint/experimental-utils';\n\nimport { PackletAnalyzer } from './PackletAnalyzer';\n\nexport type MessageIds = 'missing-readme' | 'error-reading-file' | 'readme-too-short';\ntype Options = [\n {\n minimumReadmeWords?: number;\n }\n];\n\nconst readme: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'problem',\n messages: {\n 'missing-readme':\n 'The ESLint configuration requires each packlet to provide a README.md file summarizing' +\n ' its purpose and usage: {{readmePath}}',\n 'readme-too-short':\n 'The ESLint configuration requires at least {{minimumReadmeWords}} words of documentation in the' +\n ' README.md file: {{readmePath}}',\n 'error-reading-file': 'Error reading input file {{readmePath}}:\\n{{errorMessage}}'\n },\n schema: [\n {\n type: 'object',\n properties: {\n minimumReadmeWords: {\n type: 'number'\n }\n },\n additionalProperties: false\n }\n ],\n\n docs: {\n description: 'Require each packlet folder to have a README.md file summarizing its purpose and usage',\n // Deprecated in ESLint v8; Keep for backwards compatibility\n category: 'Best Practices',\n // Too strict to be recommended in the default configuration\n recommended: false,\n url: 'https://www.npmjs.com/package/@rushstack/eslint-plugin-packlets'\n } as TSESLint.RuleMetaDataDocs\n },\n\n create: (context: TSESLint.RuleContext<MessageIds, Options>) => {\n const minimumReadmeWords: number = context.options[0]?.minimumReadmeWords || 10;\n\n // Example: /path/to/my-project/src/packlets/my-packlet/index.ts\n const inputFilePath: string = context.getFilename();\n\n // Example: /path/to/my-project/tsconfig.json\n const tsconfigFilePath: string | undefined = ESLintUtils.getParserServices(\n context\n ).program.getCompilerOptions()['configFilePath'] as string;\n\n const packletAnalyzer: PackletAnalyzer = PackletAnalyzer.analyzeInputFile(\n inputFilePath,\n tsconfigFilePath\n );\n\n if (!packletAnalyzer.nothingToDo && !packletAnalyzer.error) {\n if (packletAnalyzer.isEntryPoint) {\n return {\n Program: (node: TSESTree.Node): void => {\n const readmePath: string = path.join(\n packletAnalyzer.packletsFolderPath!,\n packletAnalyzer.inputFilePackletName!,\n 'README.md'\n );\n try {\n if (!fs.existsSync(readmePath)) {\n context.report({\n node: node,\n messageId: 'missing-readme',\n data: { readmePath }\n });\n } else {\n if (minimumReadmeWords > 0) {\n const readmeContent: string = fs.readFileSync(readmePath).toString();\n const words: string[] = readmeContent.split(/[^a-z'\"]+/i).filter((x) => x.length > 0);\n if (words.length < minimumReadmeWords) {\n context.report({\n node: node,\n messageId: 'readme-too-short',\n data: { readmePath, minimumReadmeWords }\n });\n }\n }\n }\n } catch (error) {\n context.report({\n node: node,\n messageId: 'error-reading-file',\n data: { readmePath, errorMessage: (error as Error).toString() }\n });\n }\n }\n };\n }\n }\n\n return {};\n }\n};\n\nexport { readme };\n"]}
1
+ {"version":3,"file":"readme.js","sourceRoot":"","sources":["../src/readme.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport type { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';\nimport { ESLintUtils } from '@typescript-eslint/experimental-utils';\n\nimport { PackletAnalyzer } from './PackletAnalyzer';\n\nexport type MessageIds = 'missing-readme' | 'error-reading-file' | 'readme-too-short';\ntype Options = [\n {\n minimumReadmeWords?: number;\n }\n];\n\nconst readme: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'problem',\n messages: {\n 'missing-readme':\n 'The ESLint configuration requires each packlet to provide a README.md file summarizing' +\n ' its purpose and usage: {{readmePath}}',\n 'readme-too-short':\n 'The ESLint configuration requires at least {{minimumReadmeWords}} words of documentation in the' +\n ' README.md file: {{readmePath}}',\n 'error-reading-file': 'Error reading input file {{readmePath}}:\\n{{errorMessage}}'\n },\n schema: [\n {\n type: 'object',\n properties: {\n minimumReadmeWords: {\n type: 'number'\n }\n },\n additionalProperties: false\n }\n ],\n\n docs: {\n description: 'Require each packlet folder to have a README.md file summarizing its purpose and usage',\n // Deprecated in ESLint v8; Keep for backwards compatibility\n category: 'Best Practices',\n // Too strict to be recommended in the default configuration\n recommended: false,\n url: 'https://www.npmjs.com/package/@rushstack/eslint-plugin-packlets'\n } as TSESLint.RuleMetaDataDocs\n },\n\n create: (context: TSESLint.RuleContext<MessageIds, Options>) => {\n const minimumReadmeWords: number = context.options[0]?.minimumReadmeWords || 10;\n\n // Example: /path/to/my-project/src/packlets/my-packlet/index.ts\n const inputFilePath: string = context.getFilename();\n\n // Example: /path/to/my-project/tsconfig.json\n const tsconfigFilePath: string | undefined = ESLintUtils.getParserServices(\n context\n ).program.getCompilerOptions()['configFilePath'] as string;\n\n const packletAnalyzer: PackletAnalyzer = PackletAnalyzer.analyzeInputFile(\n inputFilePath,\n tsconfigFilePath\n );\n\n if (!packletAnalyzer.nothingToDo && !packletAnalyzer.error) {\n if (packletAnalyzer.isEntryPoint) {\n return {\n Program: (node: TSESTree.Node): void => {\n const readmePath: string = path.join(\n packletAnalyzer.packletsFolderPath!,\n packletAnalyzer.inputFilePackletName!,\n 'README.md'\n );\n try {\n if (!fs.existsSync(readmePath)) {\n context.report({\n node: node,\n messageId: 'missing-readme',\n data: { readmePath }\n });\n } else {\n if (minimumReadmeWords > 0) {\n const readmeContent: string = fs.readFileSync(readmePath).toString();\n const words: string[] = readmeContent.split(/[^a-z'\"]+/i).filter((x) => x.length > 0);\n if (words.length < minimumReadmeWords) {\n context.report({\n node: node,\n messageId: 'readme-too-short',\n data: { readmePath, minimumReadmeWords }\n });\n }\n }\n }\n } catch (error) {\n context.report({\n node: node,\n messageId: 'error-reading-file',\n data: { readmePath, errorMessage: (error as Error).toString() }\n });\n }\n }\n };\n }\n }\n\n return {};\n }\n};\n\nexport { readme };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rushstack/eslint-plugin-packlets",
3
- "version": "0.3.5",
3
+ "version": "0.4.1",
4
4
  "description": "A lightweight alternative to NPM packages for organizing source files within a single project",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,28 +18,28 @@
18
18
  "main": "lib/index.js",
19
19
  "typings": "lib/index.d.ts",
20
20
  "dependencies": {
21
- "@rushstack/tree-pattern": "0.2.2",
22
- "@typescript-eslint/experimental-utils": "~5.6.0"
21
+ "@rushstack/tree-pattern": "0.2.4",
22
+ "@typescript-eslint/experimental-utils": "~5.20.0"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@rushstack/heft": "0.44.2",
29
- "@rushstack/heft-node-rig": "1.7.1",
28
+ "@rushstack/heft": "0.45.6",
29
+ "@rushstack/heft-node-rig": "1.9.7",
30
30
  "@types/eslint": "8.2.0",
31
31
  "@types/estree": "0.0.50",
32
32
  "@types/heft-jest": "1.0.1",
33
33
  "@types/node": "12.20.24",
34
- "@typescript-eslint/parser": "~5.6.0",
35
- "@typescript-eslint/typescript-estree": "~5.6.0",
34
+ "@typescript-eslint/parser": "~5.20.0",
35
+ "@typescript-eslint/typescript-estree": "~5.20.0",
36
36
  "eslint": "~8.7.0",
37
- "typescript": "~4.5.2"
37
+ "typescript": "~4.6.3"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "heft build --clean",
41
41
  "_phase:build": "heft build --clean",
42
42
  "_phase:test": "heft test --no-build"
43
43
  },
44
- "readme": "# @rushstack/eslint-plugin-packlets\n\nPacklets provide a lightweight alternative to NPM packages for organizing source files within a single project. The formalism is validated using ESLint rules.\n\n## Motivation\n\nWhen building a large application, it's a good idea to organize source files into modules, so that their dependencies can be managed. For example, suppose an application's source files can be grouped as follows:\n\n- `src/logging/*.ts` - the logging system\n- `src/data-model/*.ts` - the data model\n- `src/reports/*.ts` - the report engine\n- `src/*.ts` - other arbitrary files such as startup code and the main application\n\nUsing file folders is helpful, but it's not very strict. Files under `src/logging` can easily import files from `/src/reports`, creating a confusing circular import. They can also import arbitrary application files. Also, there is no clear distinction between which files are the \"public API\" for `src/logging` versus its private implementation details.\n\nAll these problems can be solved by reorganizing the project into NPM packages (or [Rush projects](https://rushjs.io/)). Something like this:\n\n- `@my-app/logging` - the logging system\n- `@my-app/data-model` - the data model\n- `@my-app/reports` - the report engine\n- `@my-app/application` - other arbitrary files such as startup code and the main application\n\nHowever, separating code in this way has some downsides. The projects need to build separately, which has some tooling costs (for example, \"watch mode\" now needs to consider multiple projects). In a large monorepo, the library may attract other consumers, before the API has been fully worked out.\n\nPacklets provide a lightweight alternative that offers many of the same benefits of packages, but without the `package.json` file. It's a great way to prototype your project organization before later graduating your packlets into proper NPM packages.\n\n## 5 rules for packlets\n\nWith packlets, our folders would be reorganized as follows:\n\n- `src/packlets/logging/*.ts` - the logging system\n- `src/packlets/data-model/*.ts` - the data model\n- `src/packlets/reports/*.ts` - the report engine\n- `src/*.ts` - other arbitrary files such as startup code and the main application\n\nThe [packlets-tutorial](https://github.com/microsoft/rushstack-samples/tree/main/other/packlets-tutorial) sample project illustrates this layout in full detail.\n\nThe basic design can be summarized in 5 rules:\n\n1. A \"packlet\" is defined to be a folder path `./src/packlets/<packlet-name>/index.ts`. The **index.ts** file will have the exported APIs. The `<packlet-name>` name must consist of lower case words separated by hyphens, similar to an NPM package name.\n\n Example file paths:\n ```\n src/packlets/controls\n src/packlets/logger\n src/packlets/my-long-name\n ```\n\n > **NOTE:** The `packlets` cannot be nested deeper in the tree. Like with NPM packages, `src/packlets` is a flat namespace.\n\n2. Files outside the packlet folder can only import the packlet root **index.ts**:\n\n **src/app/App.ts**\n ```ts\n // Okay\n import { MainReport } from '../packlets/reports';\n\n // Error: The import statement does not use the packlet's entry point (@rushstack/packlets/mechanics)\n import { MainReport } from '../packlets/reports/index';\n\n // Error: The import statement does not use the packlet's entry point (@rushstack/packlets/mechanics)\n import { MainReport } from '../packlets/reports/MainReport';\n ```\n\n3. Files inside a packlet folder should import their siblings directly, not via their own **index.ts** (which might create a circular reference):\n\n **src/packlets/logging/Logger.ts**\n ```ts\n // Okay\n import { MessageType } from \"./MessageType\";\n\n // Error: Files under a packlet folder must not import from their own index.ts file (@rushstack/packlets/mechanics)\n import { MessageType } from \".\";\n\n // Error: Files under a packlet folder must not import from their own index.ts file (@rushstack/packlets/mechanics)\n import { MessageType} from \"./index\";\n ```\n\n\n4. Packlets may reference other packlets, but not in a way that would introduce a circular dependency:\n\n **src/packlets/data-model/DataModel.ts**\n ```ts\n // Okay\n import { Logger } from '../../packlets/logging';\n ```\n\n **src/packlets/logging/Logger.ts**\n ```ts\n // Error: Packlet imports create a circular reference: (@rushstack/packlets/circular-deps)\n // \"logging\" is referenced by src/packlets/data-model/DataModel.ts\n // \"data-model\" is referenced by src/packlets/logging/Logger.ts\n import { DataModel } from '../../packlets/data-model';\n ```\n\n5. Other source files are allowed outside the **src/packlets** folder. They may import a packlet, but packlets must only import from other packlets or NPM packages.\n\n **src/app/App.ts**\n\n ```ts\n // Okay\n import { MainReport } from '../packlets/reports';\n ```\n\n **src/packlets/data-model/ExampleModel.ts**\n ```ts\n // Error: A local project file cannot be imported. A packlet's dependencies must be\n // NPM packages and/or other packlets. (@rushstack/packlets/mechanics)\n import { App } from '../../app/App';\n ```\n\n\n## Getting Started\n\nTo enable packlet validation for a simple `typescript-eslint` setup, reference the `@rushstack/eslint-plugin-packlets` project like this:\n\n**\\<my-project\\>/.eslintrc.js**\n```js\nmodule.exports = {\n root: true,\n parser: '@typescript-eslint/parser',\n plugins: ['@typescript-eslint'],\n extends: [\n 'eslint:recommended',\n 'plugin:@typescript-eslint/recommended',\n 'plugin:@rushstack/eslint-plugin-packlets/recommended' // <--- ADD THIS\n ],\n parserOptions: {\n project: './tsconfig.json',\n sourceType: 'module',\n tsconfigRootDir: __dirname\n }\n};\n```\n\nOr, if you are using the [@rushstack/eslint-config](https://www.npmjs.com/package/@rushstack/eslint-config) ruleset, add the `\"packlets\"` mixin like this:\n\n**\\<my-project\\>/.eslintrc.js**\n```ts\n// This is a workaround for https://github.com/eslint/eslint/issues/3458\nrequire('@rushstack/eslint-config/patch/modern-module-resolution');\n\nmodule.exports = {\n extends: [\n \"@rushstack/eslint-config/profile/node\",\n \"@rushstack/eslint-config/mixins/packlets\" // <--- ADD THIS\n ],\n parserOptions: { tsconfigRootDir: __dirname }\n};\n```\n\nThe `@rushstack/eslint-plugin-packlets` plugin implements three separate rules:\n\n- `@rushstack/packlets/mechanics` - validates most of the import path rules outlined above.\n- `@rushstack/packlets/circular-deps` - detects circular dependencies between packlets. This rule requires an ESLint configuration that enables full type information from the TypeScript compiler.\n- `@rushstack/packlets/readme` - requires each packlet to have a README.md file. This rule is disabled by default.\n\n## Requiring a README.md file\n\nIf you'd like to require a README.md file in each packlet folder, enable the optional `@rushstack/packlets/readme` rule.\n\nThe `minimumReadmeWords` option allows you to specify a minimum number of words of documentation in the README.md file. The default value is `10` words.\n\nExample configuration with the `@rushstack/packlets/readme` rule enabled:\n\n**\\<my-project\\>/.eslintrc.js**\n```ts\n// This is a workaround for https://github.com/eslint/eslint/issues/3458\nrequire('@rushstack/eslint-config/patch/modern-module-resolution');\n\nmodule.exports = {\n extends: [\n \"@rushstack/eslint-config/profile/node\",\n \"@rushstack/eslint-config/mixins/packlets\"\n ],\n parserOptions: { tsconfigRootDir: __dirname },\n overrides: [\n {\n files: ['*.ts', '*.tsx'],\n\n rules: {\n '@rushstack/packlets/readme': [ // <--- ADD THIS\n 'warn',\n { minimumReadmeWords: 10 }\n ]\n }\n }\n ]\n};\n```\n\n## Links\n\n- [CHANGELOG.md](\n https://github.com/microsoft/rushstack/blob/master/stack/eslint-plugin-packlets/CHANGELOG.md) - Find\n out what's new in the latest version\n- [@rushstack/eslint-config](https://www.npmjs.com/package/@rushstack/eslint-config) documentation\n\n`@rushstack/eslint-plugin-packlets` is part of the [Rush Stack](https://rushstack.io/) family of projects.\nThe idea for packlets was originally proposed by [@bartvandenende-wm](https://github.com/bartvandenende-wm)\nand [@victor-wm](https://github.com/victor-wm).\n"
44
+ "readme": "# @rushstack/eslint-plugin-packlets\n\nPacklets provide a lightweight alternative to NPM packages for organizing source files within a single project. The formalism is validated using ESLint rules.\n\n## Motivation\n\nWhen building a large application, it's a good idea to organize source files into modules, so that their dependencies can be managed. For example, suppose an application's source files can be grouped as follows:\n\n- `src/logging/*.ts` - the logging system\n- `src/data-model/*.ts` - the data model\n- `src/reports/*.ts` - the report engine\n- `src/*.ts` - other arbitrary files such as startup code and the main application\n\nUsing file folders is helpful, but it's not very strict. Files under `src/logging` can easily import files from `/src/reports`, creating a confusing circular import. They can also import arbitrary application files. Also, there is no clear distinction between which files are the \"public API\" for `src/logging` versus its private implementation details.\n\nAll these problems can be solved by reorganizing the project into NPM packages (or [Rush projects](https://rushjs.io/)). Something like this:\n\n- `@my-app/logging` - the logging system\n- `@my-app/data-model` - the data model\n- `@my-app/reports` - the report engine\n- `@my-app/application` - other arbitrary files such as startup code and the main application\n\nHowever, separating code in this way has some downsides. The projects need to build separately, which has some tooling costs (for example, \"watch mode\" now needs to consider multiple projects). In a large monorepo, the library may attract other consumers, before the API has been fully worked out.\n\nPacklets provide a lightweight alternative that offers many of the same benefits of packages, but without the `package.json` file. It's a great way to prototype your project organization before later graduating your packlets into proper NPM packages.\n\n## 5 rules for packlets\n\nWith packlets, our folders would be reorganized as follows:\n\n- `src/packlets/logging/*.ts` - the logging system\n- `src/packlets/data-model/*.ts` - the data model\n- `src/packlets/reports/*.ts` - the report engine\n- `src/*.ts` - other arbitrary files such as startup code and the main application\n\nThe [packlets-tutorial](https://github.com/microsoft/rushstack-samples/tree/main/other/packlets-tutorial) sample project illustrates this layout in full detail.\n\nThe basic design can be summarized in 5 rules:\n\n1. A \"packlet\" is defined to be a folder path `./src/packlets/<packlet-name>/index.ts`. The **index.ts** file will have the exported APIs. The `<packlet-name>` name must consist of lower case words separated by hyphens, similar to an NPM package name.\n\n Example file paths:\n ```\n src/packlets/controls\n src/packlets/logger\n src/packlets/my-long-name\n ```\n\n > **NOTE:** The `packlets` cannot be nested deeper in the tree. Like with NPM packages, `src/packlets` is a flat namespace.\n\n2. Files outside the packlet folder can only import the packlet root **index.ts**:\n\n **src/app/App.ts**\n ```ts\n // Okay\n import { MainReport } from '../packlets/reports';\n\n // Error: The import statement does not use the packlet's entry point (@rushstack/packlets/mechanics)\n import { MainReport } from '../packlets/reports/index';\n\n // Error: The import statement does not use the packlet's entry point (@rushstack/packlets/mechanics)\n import { MainReport } from '../packlets/reports/MainReport';\n ```\n\n3. Files inside a packlet folder should import their siblings directly, not via their own **index.ts** (which might create a circular reference):\n\n **src/packlets/logging/Logger.ts**\n ```ts\n // Okay\n import { MessageType } from \"./MessageType\";\n\n // Error: Files under a packlet folder must not import from their own index.ts file (@rushstack/packlets/mechanics)\n import { MessageType } from \".\";\n\n // Error: Files under a packlet folder must not import from their own index.ts file (@rushstack/packlets/mechanics)\n import { MessageType} from \"./index\";\n ```\n\n\n4. Packlets may reference other packlets, but not in a way that would introduce a circular dependency:\n\n **src/packlets/data-model/DataModel.ts**\n ```ts\n // Okay\n import { Logger } from '../../packlets/logging';\n ```\n\n **src/packlets/logging/Logger.ts**\n ```ts\n // Error: Packlet imports create a circular reference: (@rushstack/packlets/circular-deps)\n // \"logging\" is referenced by src/packlets/data-model/DataModel.ts\n // \"data-model\" is referenced by src/packlets/logging/Logger.ts\n import { DataModel } from '../../packlets/data-model';\n ```\n\n5. Other source files are allowed outside the **src/packlets** folder. They may import a packlet, but packlets must only import from other packlets or NPM packages.\n\n **src/app/App.ts**\n\n ```ts\n // Okay\n import { MainReport } from '../packlets/reports';\n ```\n\n **src/packlets/data-model/ExampleModel.ts**\n ```ts\n // Error: A local project file cannot be imported. A packlet's dependencies must be\n // NPM packages and/or other packlets. (@rushstack/packlets/mechanics)\n import { App } from '../../app/App';\n ```\n\n\n## Getting Started\n\nTo enable packlet validation for a simple `typescript-eslint` setup, reference the `@rushstack/eslint-plugin-packlets` project like this:\n\n**\\<my-project\\>/.eslintrc.js**\n```js\nmodule.exports = {\n root: true,\n parser: '@typescript-eslint/parser',\n plugins: ['@typescript-eslint'],\n extends: [\n 'eslint:recommended',\n 'plugin:@typescript-eslint/recommended',\n 'plugin:@rushstack/eslint-plugin-packlets/recommended' // <--- ADD THIS\n ],\n parserOptions: {\n project: './tsconfig.json',\n sourceType: 'module',\n tsconfigRootDir: __dirname\n }\n};\n```\n\nOr, if you are using the [@rushstack/eslint-config](https://www.npmjs.com/package/@rushstack/eslint-config) ruleset, add the `\"packlets\"` mixin like this:\n\n**\\<my-project\\>/.eslintrc.js**\n```ts\n// This is a workaround for https://github.com/eslint/eslint/issues/3458\nrequire('@rushstack/eslint-config/patch/modern-module-resolution');\n\nmodule.exports = {\n extends: [\n \"@rushstack/eslint-config/profile/node\",\n \"@rushstack/eslint-config/mixins/packlets\" // <--- ADD THIS\n ],\n parserOptions: { tsconfigRootDir: __dirname }\n};\n```\n\nThe `@rushstack/eslint-plugin-packlets` plugin implements three separate rules:\n\n- `@rushstack/packlets/mechanics` - validates most of the import path rules outlined above.\n- `@rushstack/packlets/circular-deps` - detects circular dependencies between packlets. This rule requires an ESLint configuration that enables full type information from the TypeScript compiler.\n- `@rushstack/packlets/readme` - requires each packlet to have a README.md file. This rule is disabled by default.\n\n## Requiring a README.md file\n\nIf you'd like to require a README.md file in each packlet folder, enable the optional `@rushstack/packlets/readme` rule.\n\nThe `minimumReadmeWords` option allows you to specify a minimum number of words of documentation in the README.md file. The default value is `10` words.\n\nExample configuration with the `@rushstack/packlets/readme` rule enabled:\n\n**\\<my-project\\>/.eslintrc.js**\n```ts\n// This is a workaround for https://github.com/eslint/eslint/issues/3458\nrequire('@rushstack/eslint-config/patch/modern-module-resolution');\n\nmodule.exports = {\n extends: [\n \"@rushstack/eslint-config/profile/node\",\n \"@rushstack/eslint-config/mixins/packlets\"\n ],\n parserOptions: { tsconfigRootDir: __dirname },\n overrides: [\n {\n files: ['*.ts', '*.tsx'],\n\n rules: {\n '@rushstack/packlets/readme': [ // <--- ADD THIS\n 'warn',\n { minimumReadmeWords: 10 }\n ]\n }\n }\n ]\n};\n```\n\n## Links\n\n- [CHANGELOG.md](\n https://github.com/microsoft/rushstack/blob/main/stack/eslint-plugin-packlets/CHANGELOG.md) - Find\n out what's new in the latest version\n- [@rushstack/eslint-config](https://www.npmjs.com/package/@rushstack/eslint-config) documentation\n\n`@rushstack/eslint-plugin-packlets` is part of the [Rush Stack](https://rushstack.io/) family of projects.\nThe idea for packlets was originally proposed by [@bartvandenende-wm](https://github.com/bartvandenende-wm)\nand [@victor-wm](https://github.com/victor-wm).\n"
45
45
  }