@alint-js/languages 0.3.2 → 0.5.0

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
@@ -59,6 +59,36 @@ plugin_languages = "./node_modules/@alint-js/languages"
59
59
  The directory lock records physical identity, so upgrading the package re-locks it. Re-run
60
60
  `alint plugin install` when it says the target changed.
61
61
 
62
+ ## Add a language
63
+
64
+ This package reads the prebuilt grammars in `tree-sitter-wasms`. That dependency supplies 36
65
+ grammars, and this package uses three of them (by the time this is written). A new language needs a query, not a grammar. The
66
+ query maps the node names of the grammar to the six captures that `extract.ts` reads.
67
+
68
+ To add a language, do these steps:
69
+
70
+ 1. Add the file name of the grammar to `GRAMMAR` in `src/grammar.ts`.
71
+ 2. Add the query to `QUERIES` in `src/queries.ts`.
72
+ 3. Add a case to `isExported` in `src/extract.ts`.
73
+ 4. Add a `LanguageDefinition` to `src/index.ts`.
74
+ 5. Add the new language to `languagesPlugin` in the same file.
75
+ 6. Add a test for the new language to `src/extract.test.ts`.
76
+
77
+ Note: `LanguageId` comes from the keys of `GRAMMAR`. After step 1, the code does not compile until
78
+ you complete step 2 and step 3. The compiler gives no error for step 4, step 5, or step 6.
79
+
80
+ To find the node names of a grammar, read the `grammar.js` file of that tree-sitter parser.
81
+
82
+ CAUTION: Make sure that the grammar uses these three node names. If the grammar uses other names,
83
+ the code still compiles and `FunctionInfo` is wrong:
84
+
85
+ - `block` is the node type of a branch or a loop (`holdsBlock` in `src/extract.ts`).
86
+ - `body` is the field name for the body of a function (`bodyStatements`).
87
+ - `name` is the field name for the name of a function (`withOwnName`).
88
+
89
+ Go, Python, and Rust use these three names. Other grammars use other names. For example, TypeScript
90
+ calls the block `statement_block`.
91
+
62
92
  ## When to use
63
93
 
64
94
  - Your project has Go, Python or Rust files and you want rules to see functions in them rather than
package/dist/index.d.mts CHANGED
@@ -747,7 +747,7 @@ interface Config<TIssue extends BaseIssue<unknown>> {
747
747
  readonly abortPipeEarly?: boolean | undefined;
748
748
  }
749
749
  //#endregion
750
- //#region ../core/dist/types-deBCDA4_.d.mts
750
+ //#region ../core/dist/types-BK5CADYK.d.mts
751
751
  //#region src/config/types.d.ts
752
752
  type ModelSize = 'large' | 'medium' | 'small';
753
753
  interface RunnerCacheConfig {
@@ -974,8 +974,10 @@ interface RuleInferenceUsageRecord {
974
974
  totalTokens?: number;
975
975
  }
976
976
  /**
977
- * - `'any'` — every registered language, and never a failure. A rule that works from `FunctionInfo`
978
- * alone wants this: a language pack the user installs later is covered without a new release.
977
+ * - `'any'` — every language except `plaintext`, and never a failure. Plain text is what a file
978
+ * falls back to when no language claims it, so a rule asking for any language is asking for a
979
+ * real one. Rules that work from `FunctionInfo` alone want this: install another language pack
980
+ * and they cover it too, unchanged.
979
981
  * - A list of language ids — `LanguageDefinition.name` values such as `go` or `typescript`, never
980
982
  * file extensions. The rule handles exactly these. Files of other languages are skipped
981
983
  * rather than failed, so one plugin can carry rules for several languages behind one `files:`
@@ -1013,11 +1015,12 @@ type Target = DirectoryTarget | PlannedSourceTarget | ProjectTarget;
1013
1015
  //#endregion
1014
1016
  //#region src/extract.d.ts
1015
1017
  /**
1016
- * One file target carrying every call site, then one function target per function, each with the
1017
- * `FunctionInfo` a consumer would otherwise need its own parser to work out.
1018
+ * One file target holding every call site, then one function target per function. Each function
1019
+ * target carries a `FunctionInfo`, so a consumer can fingerprint or classify it without parsing
1020
+ * anything itself.
1018
1021
  *
1019
- * The language comes from the file rather than a second argument, because every target carries that
1020
- * same file and the two could then disagree. Pass the file through `withLanguage` first.
1022
+ * The language is read off the file rather than passed in as an argument. Every target carries that
1023
+ * same file, so an argument could only ever contradict it. Stamp the file with `withLanguage` first.
1021
1024
  */
1022
1025
  declare function extractTargets(file: SourceFile): Promise<SourceTarget[]>;
1023
1026
  //#endregion
package/dist/index.mjs CHANGED
@@ -93,11 +93,12 @@ const QUERIES = {
93
93
  //#region src/extract.ts
94
94
  const queries = /* @__PURE__ */ new Map();
95
95
  /**
96
- * One file target carrying every call site, then one function target per function, each with the
97
- * `FunctionInfo` a consumer would otherwise need its own parser to work out.
96
+ * One file target holding every call site, then one function target per function. Each function
97
+ * target carries a `FunctionInfo`, so a consumer can fingerprint or classify it without parsing
98
+ * anything itself.
98
99
  *
99
- * The language comes from the file rather than a second argument, because every target carries that
100
- * same file and the two could then disagree. Pass the file through `withLanguage` first.
100
+ * The language is read off the file rather than passed in as an argument. Every target carries that
101
+ * same file, so an argument could only ever contradict it. Stamp the file with `withLanguage` first.
101
102
  */
102
103
  async function extractTargets(file) {
103
104
  if (!isLanguageId(file.language)) throw new Error(`Language "${file.language}" is not provided by @alint-js/languages.`);
@@ -216,7 +217,7 @@ function holdsBlock(node) {
216
217
  }
217
218
  return false;
218
219
  }
219
- /** Reachable from outside its file. Go says it with a capital, Python with the absence of a leading underscore, Rust with `pub`. */
220
+ /** Reachable from outside its file: a capital in Go, no leading underscore in Python, `pub` in Rust. */
220
221
  function isExported(node, language, name) {
221
222
  switch (language) {
222
223
  case "go": return /^[A-Z]/.test(name);
@@ -231,7 +232,7 @@ function rangeOf(node) {
231
232
  start: node.startIndex
232
233
  };
233
234
  }
234
- /** Rebases the ranges inside `outer` onto the function's own `text`, which is where `FunctionInfo` says they point. */
235
+ /** Turns absolute file offsets into offsets within the function's own `text`, as `FunctionInfo` documents. */
235
236
  function rangesInside(ranges, outer) {
236
237
  return ranges.filter((range) => range.start >= outer.start && range.end <= outer.end).map((range) => ({
237
238
  end: range.end - outer.start,
@@ -241,10 +242,11 @@ function rangesInside(ranges, outer) {
241
242
  /**
242
243
  * Adds the function's own name to its renameable identifiers.
243
244
  *
244
- * This only has anything to add for a Go method, whose name is a `field_identifier` rather than an
245
- * `identifier`. The Go query leaves `field_identifier` out on purpose, because reading a struct
246
- * field is not renameable, but a method naming itself is. Every other name the queries capture is
247
- * already an `@identifier`, so the dedupe by start offset drops the second copy.
245
+ * In practice this only matters for Go methods, whose name is a `field_identifier`. The Go query
246
+ * skips `field_identifier` on purpose: a consumer that replaces renameable names would otherwise
247
+ * make `entry.name` and `entry.size` look identical. A method's own name is safe to replace. Every
248
+ * other name the queries capture is already an `@identifier`, so the dedupe by start offset drops
249
+ * the duplicate.
248
250
  */
249
251
  function withOwnName(ranges, nameNode, outer) {
250
252
  if (nameNode === null) return ranges;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alint-js/languages",
3
3
  "type": "module",
4
- "version": "0.3.2",
4
+ "version": "0.5.0",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.mts",
@@ -13,15 +13,15 @@
13
13
  "dist"
14
14
  ],
15
15
  "peerDependencies": {
16
- "@alint-js/core": "0.3.2"
16
+ "@alint-js/core": "0.5.0"
17
17
  },
18
18
  "dependencies": {
19
19
  "tree-sitter-wasms": "^0.1.13",
20
20
  "web-tree-sitter": "^0.24.7"
21
21
  },
22
22
  "devDependencies": {
23
- "@alint-js/core": "0.3.2",
24
- "@alint-js/plugin": "0.3.2"
23
+ "@alint-js/core": "0.5.0",
24
+ "@alint-js/plugin": "0.5.0"
25
25
  },
26
26
  "scripts": {
27
27
  "build": "tsdown",