{"id":18472763,"url":"https://github.com/gregros/parjs","last_synced_at":"2025-04-12T23:33:10.631Z","repository":{"id":39006201,"uuid":"74895126","full_name":"GregRos/parjs","owner":"GregRos","description":"JavaScript parser-combinator library","archived":false,"fork":false,"pushed_at":"2024-04-08T13:40:44.000Z","size":3993,"stargazers_count":254,"open_issues_count":20,"forks_count":16,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-14T07:42:53.394Z","etag":null,"topics":["functional-programming","javascript","parse","parser","parser-combinators","parsing","text","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GregRos.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2016-11-27T15:08:35.000Z","updated_at":"2024-04-23T16:05:14.038Z","dependencies_parsed_at":"2023-11-07T01:56:10.734Z","dependency_job_id":"e4c8beb4-1fdd-4c98-9882-b483abf1d49b","html_url":"https://github.com/GregRos/parjs","commit_stats":{"total_commits":185,"total_committers":8,"mean_commits":23.125,"dds":0.05405405405405406,"last_synced_commit":"bdb2f9ccca60181bc248db63ef51f6977405c0af"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fparjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fparjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fparjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fparjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GregRos","download_url":"https://codeload.github.com/GregRos/parjs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248647259,"owners_count":21139081,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","javascript","parse","parser","parser-combinators","parsing","text","typescript"],"created_at":"2024-11-06T10:22:14.188Z","updated_at":"2025-04-12T23:33:10.603Z","avatar_url":"https://github.com/GregRos.png","language":"TypeScript","readme":"# Parjs\n\n![build](https://github.com/GregRos/parjs/actions/workflows/parjs.push.yaml/badge.svg)\n[![codecov](https://codecov.io/github/GregRos/parjs/graph/badge.svg?flag=parjs)](https://codecov.io/github/GregRos/parjs?flags[0]=parjs)\n[![npm](https://img.shields.io/npm/v/parjs)](https://www.npmjs.com/package/parjs)\n[![Downloads](https://img.shields.io/npm/dm/parjs)](https://www.npmjs.com/package/parjs)\n[![Gzipped Size](https://img.shields.io/bundlephobia/minzip/parjs)](https://bundlephobia.com/result?p=parjs)\n\n**[🌍 ABOUT THIS REPOSITORY 🌍](#Repository)**\n\nDocumentation:\n\n-   [API Documentation](https://gregros.github.io/parjs/)\n-   [Using Parjs - Developer guide](/documentation/using-parjs.md)\n\nParjs a parser combinator library inspired by [Parsec](https://wiki.haskell.org/Parsec) and [FParsec](http://www.quanttec.com/fparsec/) (its F# adaptation), and written in TypeScript.\n\n```bash\nyarn add parjs\n```\n\n🍕 Lots of parsers!\n\n⚙️ Lots of combinators!\n\n💎 Lots of immutability!\n\n🔍 Systematically documented!\n\n🧐 Debugging features!\n\n## What is it?\n\n**PROTIP:** 🍕 is the universal symbol for _parser_.\n\nParser combinator libraries let you construct parsers from small parsers and combinators that transform those parsers by, for example, applying a parser multiple times in a row.\n\nFor example, you could have **a** parser `🍕\"fi\"` that parses the string `fi` and a combinator `⚙️exactly 2` that applies a parser exactly two times. Combining them lets you parse the string `fifi`!\n\n```typescript\n// 🍕string \"fi\" ➜ ⚙️exactly 2\nstring(\"fi\").pipe(exactly(2));\n```\n\nHere is an example that constructs a parser that parses n-tuples of numbers like `(12.5, -1, 2)`, which is impossible using other parsing techniques\u003csup\u003ecitation needed\u003c/sup\u003e.\n\n```typescript\nimport { float, string, whitespace } from \"parjs\";\nimport { between, manySepBy } from \"parjs/combinators\";\n\n// 🍕float\n//  Parses a floating point number\nconst tupleElement = float();\n\n//  🍕float ➜ ⚙️between 🍕whitespace\n//  Parses a float between whitespace\nconst paddedElement = tupleElement.pipe(between(whitespace()));\n\n//  🍕float ➜ ⚙️between 🍕whitespace ➜\n//  ⚙️until fails, separated by 🍕\",\"\n//  Parses many floats between whitespace, separated by commas.\nconst separated = paddedElement.pipe(manySepBy(\",\"));\n\n//  🍕float ➜ ⚙️between 🍕whitespace ➜\n//  ⚙️until fails, separated by 🍕\",\" ➜ ⚙️between 🍕\"(\" and 🍕\")\"\n//  Parses many floats separated by commas and surrounded by parens.\nconst surrounded = separated.pipe(between(\"(\", \")\"));\n\n//  Parses the string and print [1, 2, 3]\nconsole.log(surrounded.parse(\"(1,  2 , 3 )\"));\n```\n\n## Examples\n\nHere are some more cool examples:\n\n1. [tuple parser](./packages/parjs/examples/src/tuple.ts) ([tests](./packages/parjs/examples/spec/tuple.spec.ts))\n1. [.ini parser](./packages/parjs/examples/src/ini.ts) ([tests](./packages/parjs/examples/spec/ini.spec.ts))\n1. [JSON parser](./packages/parjs/examples/src/json.ts) ([tests](./packages/parjs/examples/spec/json.spec.ts))\n1. [Math expression parser](./packages/parjs/examples/src/math.ts) ([tests](./packages/parjs/examples/spec/math.spec.ts))\n\n## How does it work?\n\nParsers are called on an input via the `parse(input)` method and return a `result` object.\n\nParsers that succeed return some kind of value. While basic parsers return the parsed input (always a string), combinators (such as `map`) let you change the returned value to pretty much anything. It’s normal to use this feature to return an AST, the result of a calculation, and so on.\n\nIf parsing succeeded, you can access the `result.value` property to get the return value.\n\n```typescript\nconst parser = string(\"hello world\").pipe(map(text =\u003e text.length));\nconst result = parser.parse(\"hello world\");\nassert(result.value === 11);\n```\n\nHowever, doing this if parsing failed throws an exception. To check if parsing succeeded or not, use the `isOkay` property.\n\nYou can also use `toString` to get a textual description of the result.\n\n```typescript\nconst result2 = parser.parse(\"hello wrld\");\nif (result.isOkay) {\n    console.log(result.value);\n} else {\n    console.log(result.toString());\n    // Soft failure at Ln 1 Col 1\n    // 1 | hello wrld\n    //     ^expecting 'hello world'\n    // Stack: string\n}\n```\n\n### Dealing with failure\n\n`parjs` handles failure by using the SHF or 😕😬💀 system. It recognizes three kinds of failures:\n\n-   😕 **S**oft failures — A parser quickly says it’s not applicable to the input. Used to parse alternative inputs.\n-   😬 **H**ard failures — Parsing failed unexpectedly. Can only be handled by special combinators.\n-   💀 **F**atal failure — Happen when you decide and tell the parser to [halt and catch fire](\u003chttps://en.wikipedia.org/wiki/Halt_and_Catch_Fire_(computing)\u003e). They can’t be handled.\n\nParsing failures bubble up through combinators unless they’re handled, just like exceptions. Handling a failure always means backtracking to before it happened.\n\nSome combinators can upgrade soft failures to hard ones (if it says so in their documentation).\n\nFailing to parse something is a common occurrence and not exceptional in the slightest. As such, `parjs` won’t throw an exception when this happens. Instead, it will only throw exceptions if you used it incorrectly or there is a bug.\n\nThe `result` object mentioned earlier also gives the failure type via its `kind` property. It can be `OK`, `Soft`, `Hard`, or `Fatal`.\n\n```typescript\nconsole.log(result.kind); // \"Soft\"\n```\n\n#### The `reason` field\n\nThe parsing result also includes the important `reason` field which says why parsing failed and usually what input was expected.\n\nThis text appears after the `^` character in the visualization, but can also be used elsewhere. It can be specified explicitly in some cases, but will usually come from the parser’s `expecting` property.\n\n#### 😕 **S**oft failures\n\n\u003e A parser quickly says it’s not applicable to the input.\n\nYou can recover from soft failures by backtracking a constant amount. These failures are used to parse alternative inputs using lots of different combinators, like `or`:\n\n```typescript\n// 🍕\"hello\" ➜ ⚙️or 🍕\"goodbye\" ➜ ⚙️or 🍕\"blort\"\n// Parses any of the strings, \"hello\", \"goodbye\", or \"blort\"\nconst parser = string(\"hello\").pipe(or(\"goodbye\"), or(\"blort\"));\n```\n\n#### 😬 Hard failure\n\n\u003e An unexpected failure that usually indicates a syntax error.\n\nHard failures usually indicate unexpected input, such as a syntax error. These failures bubble up through multiple parsers and recovering from them can involve backtracking any number of characters.\n\nMost hard failures were soft failures in an internal parser that weren’t handled, and got upgraded by a combinator. After this happens, combinators like `⚙️or` that recover from soft failures no longer work.\n\nSequential combinators tend to do this a lot if a parser fails late in the sequence. For example:\n\n```typescript\n// 🍕\"hello \" ➜ ⚙️and then, 🍕\"world\" ➜ ⚙️or 🍕\"whatever\"\n// Parses the string \"hello \" and then the string \"world\"\n// or parses the string \"hello kittie\"\nconst helloParser = string(\"hello \").pipe(\n    then(\n        // If this parser fails, ⚙️then will upgrade\n        // it to a 😬Hard failure.\n        string(\"world\")\n    ),\n    // The ⚙️or combinator can't recover from this:\n    or(\"hello kittie\")\n);\n\nconsole.log(helloParser.parse(\"whatever\").toString());\n// Hard failure at Ln 1 Col 6\n// 1 | hello world\n//           ^expecting \"world\"\n// Stack: string \u003c then \u003c string\n```\n\nTo avoid this situation, write parsers that quickly determine if the input is for them, and combinators like `or` that will immediately apply a fallback parser instead.\n\n```typescript\nconst helloParser2 = string(\"hello \").pipe(\n    then(\n        // The 😕Soft failure in the 🍕\"world\" parser\n        // is handled immediately using ⚙️or\n        // so it doesn't reach ⚙️then\n        string(\"world\").or(\"kittie\")\n    )\n);\n```\n\nHowever, sometimes hard failures are inevitable or you can’t be bothered. In those cases, you can use `⚙️recover` which lets you downgrade the failure or even pass it off as a success.\n\n```typescript\n// Let's do the same thing as the first time:\nconst helloParser3 = string(\"hello \").pipe(\n    // ⚙️then will fail 😬Hard, like we talked about:\n    then(string(\"world\")),\n    // But then the ⚙️recover combinator will downgrade the failure:\n    recover(() =\u003e ({ kind: \"Soft\" })),\n    // So the ⚙️or combinator can be used:\n    or(\"kittie\")\n);\n```\n\nHowever, code like this is the equivalent of using `try .. catch` for control flow and should be avoided.\n\nThe `⚙️must` combinator, which validates the result of a parser, emits **😬 Hard** failures by default.\n\n#### 💀 Fatal failures\n\nA **💀 Fatal** failure is the parsing equivalent of a Halt and Catch Fire instruction and can’t be recovered from – in other words, they cause the overall parsing operation to fail immediately and control to be returned to the caller.\n\nThey act kind of like thrown exceptions, except that **parsers don’t throw exceptions for bad inputs.**\n\n`parjs` parsers will never fail this way unless **you** explicitly tell them to. One way to do this is using the `fail` basic parser. This parser fails immediately for any input and can emit any failure type.\n\n```typescript\nconst parser = fail({\n    kind: \"Fatal\"\n});\n\nconsole.log(parse.parse(\"\").toString());\n```\n\n## Cool features\n\n### Immutability\n\nIn `parjs`, parsers are functionally immutable. Once a `parjs` parser is created, it will always do the same thing and can never change. I mean, **you** could do something like this:\n\n```typescript\n// 🍕\"hello world\" ➜ predicate `() =\u003e Math.random() \u003e 0.5`\nstring(\"hello world\").pipe(must(() =\u003e Math.random() \u003e 0.5));\n```\n\nBut then it’s on **you**. And _you know what you did._\n\n### Unicode support\n\nJavaScript supports Unicode strings, including **”抱き枕”**, **”כחול”**, and **”tủ lạnh”**. Those characters aren’t ASCII – most of them have character codes in the low thousands.\n\nThat doesn’t matter if you’re parsing a specific string, since it ends up being a binary comparison, but it definitely does if you’re trying to parse 4 _letters_, a broad Unicode category that includes thousands of characters.\n\nLuckily, `parjs` has got you covered. Parsers such as `letter`, have Unicode versions – `uniLetter`. These Unicode versions use the package [`char-info`](https://www.npmjs.com/package/char-info) to figure out if each character is a letter or not.\n\nThis probably involves a lookup in some complicated data structure for each potential letter.\n\n```typescript\n// 🍕ᵘLetter\n// Parses any unicode letter\nconst pNameChar = uniLetter();\n\n// 🍕ᵘLetter ➜ ⚙️until it fails\n// Parses any number of unicode letterss\nconst pName = pNameChar.pipe(many());\n\n// 🍕\"שלום שמי \" ➜ ⚙️and then, 🍕ᵘLetter ➜ ⚙️until it fails\nconst greeting = string(`שלום שמי `).pipe(qthen(pName));\n\nassert(greeting.parser(\"שלום, שמי גרג\").value === \"גרג\");\n```\n\n### Shorthand for literal parsers\n\n**Parsers accept strings**, but **combinators accept other parsers**. This is to make sure they’re as general as possible. However, in practice, a lot of their inputs are going to be stuff like `string(“hello”)`.\n\n`parjs` knows about this, and will automatically convert string literals into parsers that parse those literals.\n\n```typescript\n// 🍕\"ice \" ➜ ⚙️and then, 🍕one or more spaces\n// ➜ ⚙️and then, the regexp /\\s*baby/\nstring(\"ice\").pipe(\n    thenq(spaces1()),\n    then(\"ice\"), // Implicitly: string(\"ice \")\n    then(/\\s*baby/) // Implicitly: regexp(/\\s*baby/)\n);\n```\n\n#### Constant type inference\n\nIf you're using TypeScript, you may want to keep using `string(\"world\")` instead of `\"world\"`. This is because the former will infer the type of the parser to be `Parjser\u003c\"world\"\u003e`, while the latter will infer it to be `Parjser\u003cstring\u003e`.\n\nHere's an example of the difference:\n\n```typescript\n// This will infer \"world\" to the constant type of \"world\"\nconst parser: Parjser\u003c[\"hello\", \"world\"]\u003e = string(\"hello\").pipe(then(string(\"world\")));\n\n// This will infer to the string type, which may be more confusing to debug, and\n// have issues with type aliases\nconst parser: Parjser\u003c[\"hello\", string]\u003e = string(\"hello\").pipe(then(\"world\"));\n```\n\n### Debugging\n\n\u003e 🆕 new in version `1.0.0`\n\nThe `.debug()` method is a powerful tool for debugging parsers in your code.\n\nWhen you call `.debug()` on a parser, it enables debug mode for that parser (and returns itself). In debug mode, the parser logs detailed information about its operation, which can help you understand how it's processing the input and where it might be going wrong.\n\nHere's an example of how to use it:\n\n```typescript\nconst parser: Parjser\u003c\"a\"\u003e = string(\"a\").expects(\"an 'a' character\").debug();\n\n// when you execute the parser:\nparser.parse(\"a\");\n\n// it will console.log() something like this:\n//\n// \"consumed 'a' (length 1)\n// at position 0-\u003e1\n// 👍🏻 (Ok)\n// {\n//     \"input\": \"a\",\n//     \"userState\": {},\n//     \"position\": 1,\n//     \"stack\": [],\n//     \"value\": \"a\",\n//     \"kind\": \"OK\"\n// }\n// {\n//     \"expecting\": \"an 'a' character\",\n//     \"type\": \"string\"\n// }\"\n```\n\nWhen you execute this parser, it will log information about how it's trying to match \"hello\" in the input.\n\nRemember that `.debug()` affects only the parser it's called on. If you have a complex parser built from many smaller parsers and you call `.debug()` on the complex parser, it won't enable debug mode for the smaller parsers. If you want to debug the smaller parsers, you need to call `.debug()` on each of them. This way you can customize the debugging output to show only the information you need.\n\n### User state\n\nUser state can help you to parse complex languages, like mathematical expressions with operator precedence and languages like XML where you need to match up an end tag to a start tag.\n\nEvery time you invoke the `.parse` method `parjs` creates a unique, mutable user state object. The object is propagated throughout the parsing process and some combinators and building block parsers allow you to modify it or inspect it. It’s called _user_ state because the library will never modify it by itself.\n\nThe `.parse` method accepts an additional parameter `initialState` that contains properties and methods that are merged with the user state:\n\n```typescript\n// p is called with a parser state initialized with properties and methods.\nlet example = p.parse(\"hello\", {token: \"hi\", method() {return 1;});\n```\n\nThe combinator `map` is a projection combinator. You can give it a function taking two parameters: the parser result and the parser state.\n\n```typescript\nlet example = string(\"a\").pipe(map((result, state) =\u003e state.flag));\n```\n\n`each` is a combinator that doesn't change the parser result, so you can use it to only modify the user state.\n\n#### Replacing user state\n\nThe combinator `replaceState` lets you _replace_ the user state object, but only in the scope of the parser it applied to.\n\nIt creates a brand new user state object, merged with properties from the object you specify, and gives it to the parser. Once the parser is finished, the old user state object is restored. This means you will need to use that parser's result value to communicate out of it, and it serves the isolate other parsers from what happens inside.\n\nReplacing user state is powerful, and can allow you to write recursive parsers that need a hierarchy of nested user states to work.\n\n## Writing a parser with custom low-level logic\n\nIn most cases, you should use the existing parsers and combinators to write your parser. You shouldn't automatically write a custom parser like this.\n\nWriting a parser with totally custom logic lets you read the input and manage the position directly. This can allow you to implement new kinds of building-block parsers. While Parjs is meant to be easily extensible, this API will probably change more than more outward facing APIs, so be warned.\n\n### Parser flow\n\nWhen parsing, a unique mutable `ParsingState` object is created. This object has the following shape:\n\n```typescript\ninterface ParsingState {\n    readonly input: string;\n    position: number;\n    value: unknown;\n    userState: UserState;\n    reason: string;\n    kind: ReplyKind;\n    //...\n}\n```\n\nEach parser gets handed this object and needs to mutate its properties to return information and change the position.\n\nThe `kind`, `value`, and `reason` properties are used to send data out of the parser.\n\n1. The `kind` gives the result type: success, failure, and which type of failure.\n2. `value` is used to output the parser result. It must be assigned if the `kind` is `OK`, and must not be assigned otherwise.\n3. `reason` is used to communicate the reason for an error, if any. You should only set it in case of an error. If you don't set it and signal an error, the `expecting` field in your parser object will be used as the error.\n\nYou can modify the other properties too, except for `input` which you almost certainly should not modify.\n\n### Creating the parser\n\nTo create a custom `Parjs` parser you need to extend the class `ParjserBase`, which you import from `parjs/internal`.\n\n-   Override the `_apply` method to set the logic of the parser (this method is the one that takes the parsing state above).\n-   You also need to set `expecting` which is a default error string the parser will use in case of error.\n-   Finally, set the `type` string of the parser. This string is used to identify the parser and isn't only for informational purposes. It could be used in things like optimizations for example.\n\nHere is a simple implementation of the `eof` parser, which detects the end of the input. Add type annotations as desired.\n\n```typescript\nimport { ParjserBase, ParsingState } from \"parjs/internal\";\n\n/**\n * Returns a parser that succeeds if there is no more input.\n *\n * @param result Optionally, the result the parser will yield. Defaults to undefined.\n */\nexport function eof\u003cT\u003e(result?: T): Parjser\u003cT\u003e {\n    return new (class Eof extends ParjserBase\u003cT\u003e {\n        type = \"eof\";\n        expecting = \"expecting end of input\";\n\n        _apply(ps: ParsingState): void {\n            if (ps.position === ps.input.length) {\n                ps.kind = ResultKind.Ok;\n                ps.value = result;\n            } else {\n                ps.kind = ResultKind.SoftFail;\n            }\n        }\n    })();\n}\n```\n\n# Repository\n\nThere are a number of systems to help organize the monorepo, run commands, build, develop, and test things conveniently. Let's look at them.\n\n### VS Code Workspace\n\nOpening the file `parjs.code-workspace` in VS Code lets you work on any and all of the packages inside the repository. This workspace has three **workspace roots:**\n\n-   **parjs** at `/packages/parjs`\n-   **char-info** at `/packages/char-info`\n-   **root** at `/`, but excluding the packages folder.\n\nHere is how it looks on my highly customized installation:\n\u003cimg src=\"https://github.com/GregRos/parjs/assets/1788329/5f205e09-e941-4090-abfd-a56aa45e2ae8\" width=300\u003e\n\n### Yarn Workspaces\n\nThe yarn root `/package.json` has four [yarn workspaces](https://yarnpkg.com/features/workspaces) with a `package.json` each:\n\n-   `/packages/parjs`\n-   `/packages/char-info`\n-   `/packages/parjs/examples`\n-   `/packages/char-info/examples` 👈this is unused\n\n#### Examples Workspaces\n\nI made the examples folders separate workspace so they can import the package by name, instead of using relative imports, but so that within the repo it will still link to the current codebase.\n\n#### Executing commands\n\nCommands can be executed with `yarn` normally by going into a workspace root like `packages/parjs`.\n\nHowever, you can also use the [workspace](https://yarnpkg.com/cli/workspace) and [workspaces](https://yarnpkg.com/cli/workspaces/foreach) action groups to execute commands on multiple packages. For example:\n\n```bash\nyarn workspaces foreach -A run clean\n```\n\nTo execute a command on a single workspace, you can use, for example:\n\n```bash\nyarn workspace parjs run build\n```\n\n#### Adding dependencies\n\nIf you run the following in the repo root, you'll add the package to the workspace:\n\n```bash\nyarn add X\n```\n\nThis means it won't be a dependency of any of the packages inside it, but rather something that comes with this repo.\n\n-   If it's a dev dependency, this is usually what you want right now.\n-   If it's not, it's usually **not** what you want.\n\n#### Adding dependencies in child workspaces\n\nOne way to run commands or add dependencies is to `cd` into a folder with a `package.json` and run a normal `yarn` command there.\n\n```bash\ncd packages/parjs\nyarn run build\n```\n\nYou can also use yarn's CLI. To add a dependency to a specific package:\n\n```bash\nyarn workspace parjs add -D npm-run-all\n```\n\nIf you instead want to add it to all packages:\n\n```bash\nyarn workspaces foreach -A add -D npm-run-all\n```\n\nIn this case, it's a dev dependency. It seems to be needed in each package.\n\n### TypeScript Projects\n\n| Symbol | Meaning                                                                                   |\n| ------ | ----------------------------------------------------------------------------------------- |\n| 🏭     | `tsconfig.json` that compiles and emits JavaScript.                                       |\n| 🚀     | `tsconfig.json` that doesn't emit for tests that should be run with `ts-node` or similar. |\n| 🔗     | `tsconfig.json` that only has references to other projects and no files.                  |\n\nTypeScript project at 🔗`/tsconfig.json`, referencing:\n\n-   🔗 `/packages/parjs/tsconfig.json`, referencing\n    -   🏭 `/packages/parjs/src/tsconfig.json`\n    -   🚀 `/packages/parjs/spec/tsconfig.json`\n    -   🔗 `/packages/parjs/examples/tsconfig.json`, referencing\n        -   🏭 `/packages/parjs/examples/src/tsconfig.json`\n        -   🚀 `/packages/parjs/examples/spec/tsconfig.json`\n-   🔗 `/packages/char-info/tsconfig.json`, referencing\n    -   🏭 `/packages/char-info/src/tsconfig.json`\n    -   🏭 `/packages/char-info/spec/tsconfig.json\n\n🏭 and 🚀 tsconfigs extend `/tsconfig.base.json`, which allows it to configure compilation for the whole project. The only other properties in the different `tsconfig`s are project-specific and set `noEmit`, `composite`, `paths`, and so on. There are no meaningful overrides.d\n\n**The whole repo can be watched using a single `tsc -b -w` at the root of the repository.** That is how `yarn run watch` works.\n\n# Megamap\n\nThe structure of the entire monorepo.\n\n| Syntax  | Meaning                 |\n| ------- | ----------------------- |\n| **👈X** | extends X               |\n| **🖇X** | references X            |\n| **⇶X**  | emits to X              |\n| **⇶∅**  | no emit                 |\n| 🏭      | emitting tsconfig       |\n| 🚀      | ts-node tsconfig        |\n| 🌲      | reference-only tsconfig |\n\n```bash\nparjs/\n├── packages/\n│   ├── parjs/\n│   │   ├── src/\n│   │   │   └── 🏭 tsconfig.json 👈/tsconfig.base.json ⇶../dist\n│   │   ├── spec/\n│   │   │   └── 🚀 tsconfig.json 👈/tsconfig.base.json ⇶∅\n│   │   ├── (dist)/\n│   │   │   └── (compiled from ../src)\n│   │   ├── examples/\n│   │   │   ├── src/\n│   │   │   │   └── 🏭 tsconfig.json 👈/tsconfig.base.json ⇶../dist\n│   │   │   ├── spec/\n│   │   │   │   └── 🚀 tsconfig.json 👈/tsconfig.base.json ⇶∅\n│   │   │   ├── (dist)/\n│   │   │   │   └── (compiled from src)\n│   │   │   └── 🔗 tsconfig.json 🖇src 🖇spec\n│   │   ├── 🔗 tsconfig.json (🖇src 🖇spec 🖇examples) ⇶∅\n│   │   ├── jest.config.mjs 🖇spec 👈/jest.root.mjs\n│   │   ├── package.json\n│   │   └── README.md // parjs readme\n│   │\n│   └── char-info/\n│       ├── src/\n│       │   └── 🏭 tsconfig.json 👈/tsconfig.base.json ⇶../dist\n│       ├── spec/\n│       │   └── 🏭 tsconfig.json 👈/tsconfig.base.json ⇶../dist-spec\n│       ├── (dist)/\n│       │   └── (compiled from ../src)\n│       ├── (dist-spec)/\n│       │   └── (compiled from ../spec)\n│       ├── examples/ // unused\n│       │   └── ...\n│       ├── package.json\n│       ├── 🔗 tsconfig.json 🖇spec 🖇src\n│       └── README.md // char-info readme\n│\n├── tsconfig.base.json // all emitting tsconfigs extend from this\n├── 🔗 tsconfig.json 🖇packages/{char-info,parjs}/tsconfig.json\n├── package.json // workspace package.json file\n├── (linting configurations)\n├── jest.root.mjs // base jest configuration\n├── yarn.lock // workspace yarn.lock file\n├── README.md // monorepo readme\n├── parjs.code-workspace\n└── .git*\n```\n\n###\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgregros%2Fparjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgregros%2Fparjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgregros%2Fparjs/lists"}