diff --git a/README.md b/README.md index 020dcada320..942474aba62 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/libraries/heft-config-file](./libraries/heft-config-file/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fheft-config-file.svg)](https://badge.fury.io/js/%40rushstack%2Fheft-config-file) | [changelog](./libraries/heft-config-file/CHANGELOG.md) | [@rushstack/heft-config-file](https://www.npmjs.com/package/@rushstack/heft-config-file) | | [/libraries/load-themed-styles](./libraries/load-themed-styles/) | [![npm version](https://badge.fury.io/js/%40microsoft%2Fload-themed-styles.svg)](https://badge.fury.io/js/%40microsoft%2Fload-themed-styles) | [changelog](./libraries/load-themed-styles/CHANGELOG.md) | [@microsoft/load-themed-styles](https://www.npmjs.com/package/@microsoft/load-themed-styles) | | [/libraries/localization-utilities](./libraries/localization-utilities/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Flocalization-utilities.svg)](https://badge.fury.io/js/%40rushstack%2Flocalization-utilities) | [changelog](./libraries/localization-utilities/CHANGELOG.md) | [@rushstack/localization-utilities](https://www.npmjs.com/package/@rushstack/localization-utilities) | +| [/libraries/lookup-by-path](./libraries/lookup-by-path/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Flookup-by-path.svg)](https://badge.fury.io/js/%40rushstack%2Flookup-by-path) | [changelog](./libraries/lookup-by-path/CHANGELOG.md) | [@rushstack/lookup-by-path](https://www.npmjs.com/package/@rushstack/lookup-by-path) | | [/libraries/module-minifier](./libraries/module-minifier/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fmodule-minifier.svg)](https://badge.fury.io/js/%40rushstack%2Fmodule-minifier) | [changelog](./libraries/module-minifier/CHANGELOG.md) | [@rushstack/module-minifier](https://www.npmjs.com/package/@rushstack/module-minifier) | | [/libraries/node-core-library](./libraries/node-core-library/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fnode-core-library.svg)](https://badge.fury.io/js/%40rushstack%2Fnode-core-library) | [changelog](./libraries/node-core-library/CHANGELOG.md) | [@rushstack/node-core-library](https://www.npmjs.com/package/@rushstack/node-core-library) | | [/libraries/operation-graph](./libraries/operation-graph/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Foperation-graph.svg)](https://badge.fury.io/js/%40rushstack%2Foperation-graph) | [changelog](./libraries/operation-graph/CHANGELOG.md) | [@rushstack/operation-graph](https://www.npmjs.com/package/@rushstack/operation-graph) | diff --git a/common/changes/@microsoft/rush/path-trie_2024-08-07-18-41.json b/common/changes/@microsoft/rush/path-trie_2024-08-07-18-41.json new file mode 100644 index 00000000000..5d33e5abf70 --- /dev/null +++ b/common/changes/@microsoft/rush/path-trie_2024-08-07-18-41.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Extract LookupByPath to @rushstack/lookup-by-path and load it from there.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@rushstack/lookup-by-path/path-trie_2024-08-07-18-41.json b/common/changes/@rushstack/lookup-by-path/path-trie_2024-08-07-18-41.json new file mode 100644 index 00000000000..0f24ffc059a --- /dev/null +++ b/common/changes/@rushstack/lookup-by-path/path-trie_2024-08-07-18-41.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/lookup-by-path", + "comment": "Extract LookupByPath from @rushstack/rush-lib.", + "type": "minor" + } + ], + "packageName": "@rushstack/lookup-by-path" +} \ No newline at end of file diff --git a/common/config/rush/nonbrowser-approved-packages.json b/common/config/rush/nonbrowser-approved-packages.json index 8288cc0dadf..0e39d85c27e 100644 --- a/common/config/rush/nonbrowser-approved-packages.json +++ b/common/config/rush/nonbrowser-approved-packages.json @@ -222,6 +222,10 @@ "name": "@rushstack/package-extractor", "allowedCategories": [ "libraries", "vscode-extensions" ] }, + { + "name": "@rushstack/lookup-by-path", + "allowedCategories": [ "libraries" ] + }, { "name": "@rushstack/rig-package", "allowedCategories": [ "libraries" ] @@ -862,7 +866,6 @@ "name": "tslint", "allowedCategories": [ "libraries", "tests" ] }, - { "name": "typescript", "allowedCategories": [ "libraries", "tests", "vscode-extensions" ] diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index 90a2811dde5..f0092ef821d 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -110,10 +110,10 @@ importers: version: file:../../../apps/heft(@types/node@18.17.15) '@rushstack/heft-lint-plugin': specifier: file:../../heft-plugins/heft-lint-plugin - version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15) + version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15) '@rushstack/heft-typescript-plugin': specifier: file:../../heft-plugins/heft-typescript-plugin - version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15) + version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15) eslint: specifier: ~8.57.0 version: 8.57.0 @@ -6229,7 +6229,7 @@ packages: - typescript dev: true - file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15): + file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-api-extractor-plugin, type: directory} id: file:../../../heft-plugins/heft-api-extractor-plugin name: '@rushstack/heft-api-extractor-plugin' @@ -6244,7 +6244,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15)(jest-environment-node@29.5.0): + file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15)(jest-environment-node@29.5.0): resolution: {directory: ../../../heft-plugins/heft-jest-plugin, type: directory} id: file:../../../heft-plugins/heft-jest-plugin name: '@rushstack/heft-jest-plugin' @@ -6278,7 +6278,7 @@ packages: - ts-node dev: true - file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15): + file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-lint-plugin, type: directory} id: file:../../../heft-plugins/heft-lint-plugin name: '@rushstack/heft-lint-plugin' @@ -6292,7 +6292,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15): + file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-typescript-plugin, type: directory} id: file:../../../heft-plugins/heft-typescript-plugin name: '@rushstack/heft-typescript-plugin' @@ -6334,6 +6334,18 @@ packages: transitivePeerDependencies: - '@types/node' + file:../../../libraries/lookup-by-path(@types/node@18.17.15): + resolution: {directory: ../../../libraries/lookup-by-path, type: directory} + id: file:../../../libraries/lookup-by-path + name: '@rushstack/lookup-by-path' + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@types/node': 18.17.15 + file:../../../libraries/node-core-library(@types/node@18.17.15): resolution: {directory: ../../../libraries/node-core-library, type: directory} id: file:../../../libraries/node-core-library @@ -6410,6 +6422,7 @@ packages: '@pnpm/dependency-path': 2.1.8 '@pnpm/link-bins': 5.3.25 '@rushstack/heft-config-file': file:../../../libraries/heft-config-file(@types/node@18.17.15) + '@rushstack/lookup-by-path': file:../../../libraries/lookup-by-path(@types/node@18.17.15) '@rushstack/node-core-library': file:../../../libraries/node-core-library(@types/node@18.17.15) '@rushstack/package-deps-hash': file:../../../libraries/package-deps-hash(@types/node@18.17.15) '@rushstack/package-extractor': file:../../../libraries/package-extractor(@types/node@18.17.15) @@ -6502,7 +6515,7 @@ packages: transitivePeerDependencies: - '@types/node' - file:../../../rigs/heft-node-rig(@rushstack/heft@0.66.19)(@types/node@18.17.15): + file:../../../rigs/heft-node-rig(@rushstack/heft@0.66.25)(@types/node@18.17.15): resolution: {directory: ../../../rigs/heft-node-rig, type: directory} id: file:../../../rigs/heft-node-rig name: '@rushstack/heft-node-rig' @@ -6512,10 +6525,10 @@ packages: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@18.17.15) '@rushstack/eslint-config': file:../../../eslint/eslint-config(eslint@8.57.0)(typescript@5.4.5) '@rushstack/heft': file:../../../apps/heft(@types/node@18.17.15) - '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15) - '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15)(jest-environment-node@29.5.0) - '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15) - '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.66.19)(@types/node@18.17.15) + '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15) + '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15)(jest-environment-node@29.5.0) + '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15) + '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.66.25)(@types/node@18.17.15) '@types/heft-jest': 1.0.1 eslint: 8.57.0 jest-environment-node: 29.5.0 @@ -6535,7 +6548,7 @@ packages: dependencies: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@18.17.15) '@rushstack/heft': file:../../../apps/heft(@types/node@18.17.15) - '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@0.66.19)(@types/node@18.17.15) + '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@0.66.25)(@types/node@18.17.15) '@types/heft-jest': 1.0.1 '@types/node': 18.17.15 eslint: 8.57.0 diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index 5f991137dbc..d443a0b9985 100644 --- a/common/config/subspaces/build-tests-subspace/repo-state.json +++ b/common/config/subspaces/build-tests-subspace/repo-state.json @@ -1,6 +1,6 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "00e9bb9c23e93f1cf44e5989a201680d8a857644", + "pnpmShrinkwrapHash": "c0913bff408b4b5b4199bb79136507654411840d", "preferredVersionsHash": "ce857ea0536b894ec8f346aaea08cfd85a5af648", - "packageJsonInjectedDependenciesHash": "bc9ec80f2538e316952305db6449bf0c8cd35bf6" + "packageJsonInjectedDependenciesHash": "41bcc1ae799e99853393c22bcf0c649e0e330e14" } diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index a58e39fe4b6..5423702e50a 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -3054,6 +3054,15 @@ importers: specifier: workspace:* version: link:../../rigs/local-node-rig + ../../../libraries/lookup-by-path: + devDependencies: + '@rushstack/heft': + specifier: workspace:* + version: link:../../apps/heft + local-node-rig: + specifier: workspace:* + version: link:../../rigs/local-node-rig + ../../../libraries/module-minifier: dependencies: '@rushstack/worker-pool': @@ -3272,6 +3281,9 @@ importers: '@rushstack/heft-config-file': specifier: workspace:* version: link:../heft-config-file + '@rushstack/lookup-by-path': + specifier: workspace:* + version: link:../lookup-by-path '@rushstack/node-core-library': specifier: workspace:* version: link:../node-core-library @@ -3454,6 +3466,9 @@ importers: '@rushstack/heft-webpack5-plugin': specifier: workspace:* version: link:../../heft-plugins/heft-webpack5-plugin + '@rushstack/lookup-by-path': + specifier: workspace:* + version: link:../lookup-by-path '@rushstack/stream-collator': specifier: workspace:* version: link:../stream-collator diff --git a/common/reviews/api/lookup-by-path.api.md b/common/reviews/api/lookup-by-path.api.md new file mode 100644 index 00000000000..f96f86ad22e --- /dev/null +++ b/common/reviews/api/lookup-by-path.api.md @@ -0,0 +1,25 @@ +## API Report File for "@rushstack/lookup-by-path" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +// @beta +export interface IPrefixMatch { + index: number; + value: TItem; +} + +// @beta +export class LookupByPath { + constructor(entries?: Iterable<[string, TItem]>, delimiter?: string); + readonly delimiter: string; + findChildPath(childPath: string): TItem | undefined; + findChildPathFromSegments(childPathSegments: Iterable): TItem | undefined; + findLongestPrefixMatch(query: string): IPrefixMatch | undefined; + static iteratePathSegments(serializedPath: string, delimiter?: string): Iterable; + setItem(serializedPath: string, value: TItem): this; + setItemFromSegments(pathSegments: Iterable, value: TItem): this; +} + +``` diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 97aba62d33c..42c92b45f12 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -15,9 +15,11 @@ import type { CommandLineParameter } from '@rushstack/ts-command-line'; import { CommandLineParameterKind } from '@rushstack/ts-command-line'; import { HookMap } from 'tapable'; import { IPackageJson } from '@rushstack/node-core-library'; +import { IPrefixMatch } from '@rushstack/lookup-by-path'; import { ITerminal } from '@rushstack/terminal'; import { ITerminalProvider } from '@rushstack/terminal'; import { JsonObject } from '@rushstack/node-core-library'; +import { LookupByPath } from '@rushstack/lookup-by-path'; import { PackageNameParser } from '@rushstack/node-core-library'; import type { StdioSummarizer } from '@rushstack/terminal'; import { SyncHook } from 'tapable'; @@ -737,13 +739,7 @@ export interface IPnpmPeerDependencyRules { ignoreMissing?: string[]; } -// @beta -export interface IPrefixMatch { - // (undocumented) - index: number; - // (undocumented) - value: TItem; -} +export { IPrefixMatch } // @internal (undocumented) export interface _IRawRepoState { @@ -894,17 +890,7 @@ export class LockStepVersionPolicy extends VersionPolicy { get version(): string; } -// @beta -export class LookupByPath { - constructor(entries?: Iterable<[string, TItem]>, delimiter?: string); - readonly delimiter: string; - findChildPath(childPath: string): TItem | undefined; - findChildPathFromSegments(childPathSegments: Iterable): TItem | undefined; - findLongestPrefixMatch(query: string): IPrefixMatch | undefined; - static iteratePathSegments(serializedPath: string, delimiter?: string): Iterable; - setItem(serializedPath: string, value: TItem): this; - setItemFromSegments(pathSegments: Iterable, value: TItem): this; -} +export { LookupByPath } // @public export class NpmOptionsConfiguration extends PackageManagerOptionsConfigurationBase { diff --git a/libraries/lookup-by-path/.eslintrc.js b/libraries/lookup-by-path/.eslintrc.js new file mode 100644 index 00000000000..0b04796d1ee --- /dev/null +++ b/libraries/lookup-by-path/.eslintrc.js @@ -0,0 +1,13 @@ +// This is a workaround for https://github.com/eslint/eslint/issues/3458 +require('local-node-rig/profiles/default/includes/eslint/patch/modern-module-resolution'); +// This is a workaround for https://github.com/microsoft/rushstack/issues/3021 +require('local-node-rig/profiles/default/includes/eslint/patch/custom-config-package-names'); + +module.exports = { + extends: [ + 'local-node-rig/profiles/default/includes/eslint/profile/node', + 'local-node-rig/profiles/default/includes/eslint/mixins/friendly-locals', + 'local-node-rig/profiles/default/includes/eslint/mixins/tsdoc' + ], + parserOptions: { tsconfigRootDir: __dirname } +}; diff --git a/libraries/lookup-by-path/.npmignore b/libraries/lookup-by-path/.npmignore new file mode 100644 index 00000000000..bc349f9a4be --- /dev/null +++ b/libraries/lookup-by-path/.npmignore @@ -0,0 +1,32 @@ +# THIS IS A STANDARD TEMPLATE FOR .npmignore FILES IN THIS REPO. + +# Ignore all files by default, to avoid accidentally publishing unintended files. +* + +# Use negative patterns to bring back the specific things we want to publish. +!/bin/** +!/lib/** +!/lib-*/** +!/dist/** + +!CHANGELOG.md +!CHANGELOG.json +!heft-plugin.json +!rush-plugin-manifest.json +!ThirdPartyNotice.txt + +# Ignore certain patterns that should not get published. +/dist/*.stats.* +/lib/**/test/ +/lib-*/**/test/ +*.test.js + +# NOTE: These don't need to be specified, because NPM includes them automatically. +# +# package.json +# README.md +# LICENSE + +# --------------------------------------------------------------------------- +# DO NOT MODIFY ABOVE THIS LINE! Add any project-specific overrides below. +# --------------------------------------------------------------------------- diff --git a/libraries/lookup-by-path/LICENSE b/libraries/lookup-by-path/LICENSE new file mode 100644 index 00000000000..ad73d857028 --- /dev/null +++ b/libraries/lookup-by-path/LICENSE @@ -0,0 +1,24 @@ +@rushstack/lookup-by-path + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libraries/lookup-by-path/README.md b/libraries/lookup-by-path/README.md new file mode 100644 index 00000000000..d450300ed73 --- /dev/null +++ b/libraries/lookup-by-path/README.md @@ -0,0 +1,14 @@ +# @rushstack/lookup-by-path + +This library contains a strongly-typed implementation of of a [Trie](https://en.wikipedia.org/wiki/Trie) (a.k.a. prefix tree) data structure optimized for file paths and URLs. + +This package is used by Rush to associate Git hashes with their nearest ancestor Rush project, for example. + +## Links + +- [CHANGELOG.md]( + https://github.com/microsoft/rushstack/blob/main/libraries/lookup-by-path/CHANGELOG.md) - Find + out what's new in the latest version +- [API Reference](https://api.rushstack.io/pages/lookup-by-path/) + +`@rushstack/lookup-by-path` is part of the [Rush Stack](https://rushstack.io/) family of projects. diff --git a/libraries/lookup-by-path/config/api-extractor.json b/libraries/lookup-by-path/config/api-extractor.json new file mode 100644 index 00000000000..996e271d3dd --- /dev/null +++ b/libraries/lookup-by-path/config/api-extractor.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + "mainEntryPointFilePath": "/lib/index.d.ts", + + "apiReport": { + "enabled": true, + "reportFolder": "../../../common/reviews/api" + }, + + "docModel": { + "enabled": true, + "apiJsonFilePath": "../../../common/temp/api/.api.json" + }, + + "dtsRollup": { + "enabled": true + } +} diff --git a/libraries/lookup-by-path/config/jest.config.json b/libraries/lookup-by-path/config/jest.config.json new file mode 100644 index 00000000000..d1749681d90 --- /dev/null +++ b/libraries/lookup-by-path/config/jest.config.json @@ -0,0 +1,3 @@ +{ + "extends": "local-node-rig/profiles/default/config/jest.config.json" +} diff --git a/libraries/lookup-by-path/config/rig.json b/libraries/lookup-by-path/config/rig.json new file mode 100644 index 00000000000..165ffb001f5 --- /dev/null +++ b/libraries/lookup-by-path/config/rig.json @@ -0,0 +1,7 @@ +{ + // The "rig.json" file directs tools to look for their config files in an external package. + // Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + "rigPackageName": "local-node-rig" +} diff --git a/libraries/lookup-by-path/package.json b/libraries/lookup-by-path/package.json new file mode 100644 index 00000000000..7903f04be36 --- /dev/null +++ b/libraries/lookup-by-path/package.json @@ -0,0 +1,37 @@ +{ + "name": "@rushstack/lookup-by-path", + "version": "0.0.0", + "description": "Strongly typed trie data structure for path and URL-like strings.", + "main": "lib/index.js", + "typings": "dist/lookup-by-path.d.ts", + "keywords": [ + "trie", + "path", + "url", + "radix tree", + "prefix tree" + ], + "license": "MIT", + "repository": { + "url": "https://github.com/microsoft/rushstack.git", + "type": "git", + "directory": "libraries/lookup-by-path" + }, + "scripts": { + "build": "heft build --clean", + "_phase:build": "heft run --only build -- --clean", + "_phase:test": "heft run --only test -- --clean" + }, + "devDependencies": { + "@rushstack/heft": "workspace:*", + "local-node-rig": "workspace:*" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } +} diff --git a/libraries/rush-lib/src/logic/test/LookupByPath.test.ts b/libraries/lookup-by-path/src/LookupByPath.test.ts similarity index 99% rename from libraries/rush-lib/src/logic/test/LookupByPath.test.ts rename to libraries/lookup-by-path/src/LookupByPath.test.ts index 2d56d082d58..06a30631ae4 100644 --- a/libraries/rush-lib/src/logic/test/LookupByPath.test.ts +++ b/libraries/lookup-by-path/src/LookupByPath.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { LookupByPath } from '../LookupByPath'; +import { LookupByPath } from './LookupByPath'; describe(LookupByPath.iteratePathSegments.name, () => { it('returns empty for an empty string', () => { diff --git a/libraries/rush-lib/src/logic/LookupByPath.ts b/libraries/lookup-by-path/src/LookupByPath.ts similarity index 76% rename from libraries/rush-lib/src/logic/LookupByPath.ts rename to libraries/lookup-by-path/src/LookupByPath.ts index ce209f909e4..949dceb72b2 100644 --- a/libraries/rush-lib/src/logic/LookupByPath.ts +++ b/libraries/lookup-by-path/src/LookupByPath.ts @@ -2,9 +2,9 @@ // See LICENSE in the project root for license information. /** - * A node in the path tree used in LookupByPath + * A node in the path trie used in LookupByPath */ -interface IPathTreeNode { +interface IPathTrieNode { /** * The value that exactly matches the current relative path */ @@ -12,11 +12,17 @@ interface IPathTreeNode { /** * Child nodes by subfolder */ - children: Map> | undefined; + children: Map> | undefined; } interface IPrefixEntry { + /** + * The prefix that was matched + */ prefix: string; + /** + * The index of the first character after the matched prefix + */ index: number; } @@ -26,24 +32,33 @@ interface IPrefixEntry { * @beta */ export interface IPrefixMatch { + /** + * The item that matched the prefix + */ value: TItem; + /** + * The index of the first character after the matched prefix + */ index: number; } /** - * This class is used to associate POSIX relative paths, such as those returned by `git` commands, - * with entities that correspond with ancestor folders, such as Rush Projects. + * This class is used to associate path-like-strings, such as those returned by `git` commands, + * with entities that correspond with ancestor folders, such as Rush Projects or npm packages. * * It is optimized for efficiently locating the nearest ancestor path with an associated value. * + * It is implemented as a Trie (https://en.wikipedia.org/wiki/Trie) data structure, with each edge + * being a path segment. + * * @example * ```ts - * const tree = new LookupByPath([['foo', 1], ['bar', 2], ['foo/bar', 3]]); - * tree.findChildPath('foo'); // returns 1 - * tree.findChildPath('foo/baz'); // returns 1 - * tree.findChildPath('baz'); // returns undefined - * tree.findChildPath('foo/bar/baz'); returns 3 - * tree.findChildPath('bar/foo/bar'); returns 2 + * const trie = new LookupByPath([['foo', 1], ['bar', 2], ['foo/bar', 3]]); + * trie.findChildPath('foo'); // returns 1 + * trie.findChildPath('foo/baz'); // returns 1 + * trie.findChildPath('baz'); // returns undefined + * trie.findChildPath('foo/bar/baz'); returns 3 + * trie.findChildPath('bar/foo/bar'); returns 2 * ``` * @beta */ @@ -53,14 +68,14 @@ export class LookupByPath { */ public readonly delimiter: string; /** - * The root node of the tree, corresponding to the path '' + * The root node of the trie, corresponding to the path '' */ - private readonly _root: IPathTreeNode; + private readonly _root: IPathTrieNode; /** * Constructs a new `LookupByPath` * - * @param entries - Initial path-value pairs to populate the tree. + * @param entries - Initial path-value pairs to populate the trie. */ public constructor(entries?: Iterable<[string, TItem]>, delimiter?: string) { this._root = { @@ -136,12 +151,12 @@ export class LookupByPath { * @returns this, for chained calls */ public setItemFromSegments(pathSegments: Iterable, value: TItem): this { - let node: IPathTreeNode = this._root; + let node: IPathTrieNode = this._root; for (const segment of pathSegments) { if (!node.children) { node.children = new Map(); } - let child: IPathTreeNode | undefined = node.children.get(segment); + let child: IPathTrieNode | undefined = node.children.get(segment); if (!child) { node.children.set( segment, @@ -166,9 +181,9 @@ export class LookupByPath { * * @example * ```ts - * const tree = new LookupByPath([['foo', 1], ['foo/bar', 2]]); - * tree.findChildPath('foo/baz'); // returns 1 - * tree.findChildPath('foo/bar/baz'); // returns 2 + * const trie = new LookupByPath([['foo', 1], ['foo/bar', 2]]); + * trie.findChildPath('foo/baz'); // returns 1 + * trie.findChildPath('foo/bar/baz'); // returns 2 * ``` */ public findChildPath(childPath: string): TItem | undefined { @@ -184,9 +199,9 @@ export class LookupByPath { * * @example * ```ts - * const tree = new LookupByPath([['foo', 1], ['foo/bar', 2]]); - * tree.findLongestPrefixMatch('foo/baz'); // returns { item: 1, index: 3 } - * tree.findLongestPrefixMatch('foo/bar/baz'); // returns { item: 2, index: 7 } + * const trie = new LookupByPath([['foo', 1], ['foo/bar', 2]]); + * trie.findLongestPrefixMatch('foo/baz'); // returns { item: 1, index: 3 } + * trie.findLongestPrefixMatch('foo/bar/baz'); // returns { item: 2, index: 7 } * ``` */ public findLongestPrefixMatch(query: string): IPrefixMatch | undefined { @@ -201,18 +216,18 @@ export class LookupByPath { * * @example * ```ts - * const tree = new LookupByPath([['foo', 1], ['foo/bar', 2]]); - * tree.findChildPathFromSegments(['foo', 'baz']); // returns 1 - * tree.findChildPathFromSegments(['foo','bar', 'baz']); // returns 2 + * const trie = new LookupByPath([['foo', 1], ['foo/bar', 2]]); + * trie.findChildPathFromSegments(['foo', 'baz']); // returns 1 + * trie.findChildPathFromSegments(['foo','bar', 'baz']); // returns 2 * ``` */ public findChildPathFromSegments(childPathSegments: Iterable): TItem | undefined { - let node: IPathTreeNode = this._root; + let node: IPathTrieNode = this._root; let best: TItem | undefined = node.value; // Trivial cases if (node.children) { for (const segment of childPathSegments) { - const child: IPathTreeNode | undefined = node.children.get(segment); + const child: IPathTrieNode | undefined = node.children.get(segment); if (!child) { break; } @@ -236,7 +251,7 @@ export class LookupByPath { * @returns the found item, or `undefined` if no item was found */ private _findLongestPrefixMatch(prefixes: Iterable): IPrefixMatch | undefined { - let node: IPathTreeNode = this._root; + let node: IPathTrieNode = this._root; let best: IPrefixMatch | undefined = node.value ? { value: node.value, @@ -246,7 +261,7 @@ export class LookupByPath { // Trivial cases if (node.children) { for (const { prefix: hash, index } of prefixes) { - const child: IPathTreeNode | undefined = node.children.get(hash); + const child: IPathTrieNode | undefined = node.children.get(hash); if (!child) { break; } diff --git a/libraries/lookup-by-path/src/index.ts b/libraries/lookup-by-path/src/index.ts new file mode 100644 index 00000000000..7cc91e2647e --- /dev/null +++ b/libraries/lookup-by-path/src/index.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * Strongly typed trie data structure for path and URL-like strings. + * + * @packageDocumentation + */ + +export type { IPrefixMatch } from './LookupByPath'; +export { LookupByPath } from './LookupByPath'; diff --git a/libraries/lookup-by-path/tsconfig.json b/libraries/lookup-by-path/tsconfig.json new file mode 100644 index 00000000000..9a79fa4af11 --- /dev/null +++ b/libraries/lookup-by-path/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "./node_modules/local-node-rig/profiles/default/tsconfig-base.json", + + "compilerOptions": { + "target": "ES2019" + } +} diff --git a/libraries/rush-lib/package.json b/libraries/rush-lib/package.json index 3a6ed77cb97..e0ac87388a7 100644 --- a/libraries/rush-lib/package.json +++ b/libraries/rush-lib/package.json @@ -32,6 +32,7 @@ "@pnpm/dependency-path": "~2.1.2", "@pnpm/link-bins": "~5.3.7", "@rushstack/heft-config-file": "workspace:*", + "@rushstack/lookup-by-path": "workspace:*", "@rushstack/node-core-library": "workspace:*", "@rushstack/package-deps-hash": "workspace:*", "@rushstack/package-extractor": "workspace:*", diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index 5ab4b13d7a7..f3fa0cbd6e4 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -14,6 +14,7 @@ import { type FileSystemStats, InternalError } from '@rushstack/node-core-library'; +import { LookupByPath } from '@rushstack/lookup-by-path'; import { trueCasePathSync } from 'true-case-path'; import { Rush } from '../api/Rush'; @@ -32,7 +33,6 @@ import { PnpmPackageManager } from './packageManager/PnpmPackageManager'; import { ExperimentsConfiguration } from './ExperimentsConfiguration'; import { PackageNameParsers } from './PackageNameParsers'; import type { RepoStateFile } from '../logic/RepoStateFile'; -import { LookupByPath } from '../logic/LookupByPath'; import { RushPluginsConfiguration } from './RushPluginsConfiguration'; import { type IPnpmOptionsJson, PnpmOptionsConfiguration } from '../logic/pnpm/PnpmOptionsConfiguration'; import { type INpmOptionsJson, NpmOptionsConfiguration } from '../logic/npm/NpmOptionsConfiguration'; diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index b3266a1559e..63e7e812116 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -6,6 +6,9 @@ * @packageDocumentation */ +// For backwards compatibility +export { LookupByPath as LookupByPath, type IPrefixMatch } from '@rushstack/lookup-by-path'; + export { ApprovedPackagesPolicy } from './api/ApprovedPackagesPolicy'; export { RushConfiguration, type ITryFindRushJsonLocationOptions } from './api/RushConfiguration'; @@ -88,7 +91,6 @@ export { export { RepoStateFile } from './logic/RepoStateFile'; -export { LookupByPath, type IPrefixMatch } from './logic/LookupByPath'; export { EventHooks, Event } from './api/EventHooks'; export { ChangeManager } from './api/ChangeManager'; diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index bd128fe2657..bc2c1408ce0 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -20,7 +20,7 @@ import { Git } from './Git'; import { BaseProjectShrinkwrapFile } from './base/BaseProjectShrinkwrapFile'; import type { RushConfigurationProject } from '../api/RushConfigurationProject'; import { RushConstants } from './RushConstants'; -import type { LookupByPath } from './LookupByPath'; +import type { LookupByPath } from '@rushstack/lookup-by-path'; import { PnpmShrinkwrapFile } from './pnpm/PnpmShrinkwrapFile'; import { UNINITIALIZED } from '../utilities/Utilities'; diff --git a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts index c5257354feb..1c328acc28b 100644 --- a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import { LookupByPath } from '@rushstack/lookup-by-path'; import { StringBufferTerminalProvider, Terminal } from '@rushstack/terminal'; import { ProjectChangeAnalyzer } from '../ProjectChangeAnalyzer'; @@ -8,7 +9,6 @@ import type { RushConfiguration } from '../../api/RushConfiguration'; import { EnvironmentConfiguration } from '../../api/EnvironmentConfiguration'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import { RushProjectConfiguration } from '../../api/RushProjectConfiguration'; -import { LookupByPath } from '../LookupByPath'; import { UNINITIALIZED } from '../../utilities/Utilities'; describe(ProjectChangeAnalyzer.name, () => { diff --git a/libraries/rush-sdk/package.json b/libraries/rush-sdk/package.json index 52e44a47dba..aa25f13aafc 100644 --- a/libraries/rush-sdk/package.json +++ b/libraries/rush-sdk/package.json @@ -48,6 +48,7 @@ "@rushstack/heft": "workspace:*", "local-node-rig": "workspace:*", "@rushstack/heft-webpack5-plugin": "workspace:*", + "@rushstack/lookup-by-path": "workspace:*", "@rushstack/stream-collator": "workspace:*", "@rushstack/ts-command-line": "workspace:*", "@rushstack/webpack-preserve-dynamic-require-plugin": "workspace:*", diff --git a/rush.json b/rush.json index 84045504de0..5728d6e9eb8 100644 --- a/rush.json +++ b/rush.json @@ -1029,6 +1029,12 @@ "reviewCategory": "libraries", "shouldPublish": true }, + { + "packageName": "@rushstack/lookup-by-path", + "projectFolder": "libraries/lookup-by-path", + "reviewCategory": "libraries", + "shouldPublish": true + }, { "packageName": "@rushstack/module-minifier", "projectFolder": "libraries/module-minifier",