@tabnas/proto 0.6.6 → 0.6.8
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 +3 -0
- package/dist/preflight.d.ts +2 -0
- package/dist/preflight.js +140 -0
- package/dist/preflight.js.map +1 -0
- package/dist/proto.d.ts +5 -2
- package/dist/proto.js +12 -3
- package/dist/proto.js.map +1 -1
- package/dist/translate.d.ts +13 -0
- package/dist/translate.js +1043 -0
- package/dist/translate.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/preflight.ts +134 -0
- package/src/proto.ts +10 -3
- package/src/translate.ts +1065 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":";AAAA,2EAA2E;;QAkiCzE,SAAS;AAjhCX,MAAM,WAAW,GAAqB,MAAM,CAAC,MAAM,CAAC;IAClD,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+CX;IACC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAw9BxD,EAAE,CAAC;CACH,CAAC,CAAA;AAEF,SAAS,SAAS;IAChB,OAAO,WAAW,CAAA;AACpB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"7.0.2","root":["../src/aggregate.ts","../src/build-descriptor.ts","../src/descriptor.ts","../src/detect-version.ts","../src/grammar.ts","../src/proto.ts"],"packageJsons":["../node_modules/@tabnas/parser/package.json","../package.json"],"missingPackageJsons":["../node_modules/@tabnas/parser/dist/package.json"]}
|
|
1
|
+
{"version":"7.0.2","root":["../src/aggregate.ts","../src/build-descriptor.ts","../src/descriptor.ts","../src/detect-version.ts","../src/grammar.ts","../src/preflight.ts","../src/proto.ts","../src/translate.ts"],"packageJsons":["../node_modules/@tabnas/parser/package.json","../package.json"],"missingPackageJsons":["../node_modules/@tabnas/parser/dist/package.json"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tabnas/proto",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "Parse Protocol Buffers .proto IDL (proto2, proto3, edition 2023/2024) into FileDescriptorProto-shaped JSON, using the Tabnas parser and an ABNF grammar.",
|
|
5
5
|
"main": "dist/proto.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"test-some": "node --enable-source-maps --test-name-pattern=\"$npm_config_pattern\" --test \"dist-test/*.test.js\"",
|
|
32
32
|
"pretest-watch": "npm run build",
|
|
33
33
|
"test-watch": "node --test --watch dist-test/proto.test.js",
|
|
34
|
-
"embed": "node embed-grammar.js",
|
|
34
|
+
"embed": "node embed-grammar.js && node embed-translate.js",
|
|
35
35
|
"gen-grammar": "node gen-grammar.js",
|
|
36
36
|
"example": "node examples/wire-poc/demo.js",
|
|
37
37
|
"preexample-test": "npm run build",
|
|
38
38
|
"example-test": "node --test examples/wire-poc/codec.test.js",
|
|
39
39
|
"watch": "tsc --build src test -w",
|
|
40
|
-
"build": "node embed-grammar.js && node prune-dist-test.js && tsc --build src && tsc --build test",
|
|
40
|
+
"build": "node embed-grammar.js && node embed-translate.js && node prune-dist-test.js && tsc --build src && tsc --build test",
|
|
41
41
|
"clean": "rm -rf node_modules dist dist-test yarn.lock package-lock.json",
|
|
42
42
|
"reset": "npm run clean && npm i && npm run build && npm test",
|
|
43
43
|
"repo-tag": "REPO_VERSION=`node -e \"console.log(require('./package').version)\"` && echo TAG: ts/v$REPO_VERSION && git commit -a -m v$REPO_VERSION && git push && git tag ts/v$REPO_VERSION && git push origin ts/v$REPO_VERSION;",
|
package/src/preflight.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/* Copyright (c) 2026 Richard Rodger and other contributors, MIT License */
|
|
2
|
+
|
|
3
|
+
// The nesting preflight: a `.proto` document nesting deeper than
|
|
4
|
+
// MAX_NESTING_DEPTH is refused before the engine builds a tree that deep.
|
|
5
|
+
//
|
|
6
|
+
// The tree costs more than its nesting suggests. A CST node's `src` holds
|
|
7
|
+
// every token beneath it, so a document nested `d` levels deep builds
|
|
8
|
+
// source text that grows with d squared, and so do the parse's time and
|
|
9
|
+
// memory: the walk here overflows its stack from about 3,000 levels, after
|
|
10
|
+
// seconds and hundreds of megabytes; Go's parse grows past any memory a
|
|
11
|
+
// machine has; and a Rust stack that runs out aborts the process.
|
|
12
|
+
// DIVERGENCE.md section 2 has the measurements. So every runtime refuses
|
|
13
|
+
// the same documents, with the same message, at the same cap: the Rust
|
|
14
|
+
// port's, which sits more than an order of magnitude past anything a
|
|
15
|
+
// hand-written `.proto` nests and matches the recursion budget protoc's
|
|
16
|
+
// own parser carries.
|
|
17
|
+
//
|
|
18
|
+
// Port of `preflight` and `brace_depth` in the Rust crate (`rs/src/lib.rs`,
|
|
19
|
+
// `rs/src/build_descriptor.rs`); `go/preflight.go` is the Go one.
|
|
20
|
+
|
|
21
|
+
// How deep a document may nest, in braces. `parse` refuses a deeper one.
|
|
22
|
+
export const MAX_NESTING_DEPTH = 100
|
|
23
|
+
|
|
24
|
+
// Refuse a `.proto` source that nests deeper than MAX_NESTING_DEPTH,
|
|
25
|
+
// before anything builds a tree that deep. `parse` runs it; a caller who
|
|
26
|
+
// drives the engine directly (`new Tabnas().use(Proto)` and `tn.parse`)
|
|
27
|
+
// runs it on the source first. Throws an Error whose message names the
|
|
28
|
+
// depth; like every refusal the plugin makes itself, it carries no code.
|
|
29
|
+
export function preflight(src: string): void {
|
|
30
|
+
const depth = braceDepth(src)
|
|
31
|
+
if (depth > MAX_NESTING_DEPTH) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`proto: document nests ${depth} levels deep, past the ` +
|
|
34
|
+
`${MAX_NESTING_DEPTH} this parser accepts`,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The grammar's keywords: its match tokens, each a word the lexer takes
|
|
40
|
+
// whole when no word character follows it. A token starts after one, so a
|
|
41
|
+
// quote straight after a keyword opens a string (`reserved"x";` reserves
|
|
42
|
+
// `x`). ts/test/preflight.test.ts holds this list to proto-grammar.json.
|
|
43
|
+
const KEYWORDS = new Set([
|
|
44
|
+
'edition', 'enum', 'export', 'extend', 'extensions', 'group', 'import',
|
|
45
|
+
'local', 'map', 'max', 'message', 'oneof', 'option', 'optional', 'package',
|
|
46
|
+
'public', 'repeated', 'required', 'reserved', 'returns', 'rpc', 'service',
|
|
47
|
+
'stream', 'syntax', 'to', 'weak',
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
// Where a token ends and the next starts: a space, a line break, or one of
|
|
51
|
+
// the grammar's fixed tokens other than the braces, which the scan counts
|
|
52
|
+
// on their own. The test holds these to proto-grammar.json too.
|
|
53
|
+
const SEPARATORS = ' \t\r\n[]:,;=()<>-.+'
|
|
54
|
+
|
|
55
|
+
// A word character, as the keywords' `(?![A-Za-z0-9_])` reads one.
|
|
56
|
+
function isWord(ch: number): boolean {
|
|
57
|
+
return (0x30 <= ch && ch <= 0x39) || (0x41 <= ch && ch <= 0x5a) ||
|
|
58
|
+
(0x61 <= ch && ch <= 0x7a) || 0x5f === ch
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The nesting depth of a `.proto` source, counted in braces, skipping the
|
|
62
|
+
// string literals and the comments the tabnas lexer skips, where it skips
|
|
63
|
+
// them:
|
|
64
|
+
//
|
|
65
|
+
// - A line comment, `//` or `#`, runs to the next line break, and the
|
|
66
|
+
// lexer breaks a line at a carriage return as well as a line feed.
|
|
67
|
+
// - A string is double, single or backtick quoted, a backslash escaping
|
|
68
|
+
// the character after it, and a backtick string runs across lines.
|
|
69
|
+
// - A quote opens a string only where the lexer starts a token: at the
|
|
70
|
+
// start, and after a space, a line break, a fixed token, a string, a
|
|
71
|
+
// comment or a keyword. Inside a word, `a"b`, the lexer reads the quote
|
|
72
|
+
// as part of the word, and the braces after it count.
|
|
73
|
+
//
|
|
74
|
+
// Over-counting is safe and under-counting is not, so an unterminated
|
|
75
|
+
// string or comment counts every brace inside it: the engine rejects that
|
|
76
|
+
// source anyway. Only ASCII characters decide anything here, so scanning
|
|
77
|
+
// UTF-16 code units counts exactly what the Rust and Go ports count
|
|
78
|
+
// scanning UTF-8 bytes.
|
|
79
|
+
function braceDepth(src: string): number {
|
|
80
|
+
const length = src.length
|
|
81
|
+
let at = 0
|
|
82
|
+
let depth = 0
|
|
83
|
+
let deepest = 0
|
|
84
|
+
// Whether a token starts at `at`.
|
|
85
|
+
let start = true
|
|
86
|
+
while (at < length) {
|
|
87
|
+
const ch = src.charCodeAt(at)
|
|
88
|
+
if (0x7b === ch) { // {
|
|
89
|
+
depth++
|
|
90
|
+
if (depth > deepest) deepest = depth
|
|
91
|
+
at++
|
|
92
|
+
start = true
|
|
93
|
+
} else if (0x7d === ch) { // }
|
|
94
|
+
if (0 < depth) depth--
|
|
95
|
+
at++
|
|
96
|
+
start = true
|
|
97
|
+
} else if (SEPARATORS.includes(src[at])) {
|
|
98
|
+
at++
|
|
99
|
+
start = true
|
|
100
|
+
} else if (0x23 === ch || (0x2f === ch && 0x2f === src.charCodeAt(at + 1))) {
|
|
101
|
+
// `#` or `//`. The shared tabnas lexer reads `#` as a line comment;
|
|
102
|
+
// `.proto` does not, which the leniency corpus records.
|
|
103
|
+
while (at < length && 0x0a !== src.charCodeAt(at) &&
|
|
104
|
+
0x0d !== src.charCodeAt(at)) {
|
|
105
|
+
at++
|
|
106
|
+
}
|
|
107
|
+
start = true
|
|
108
|
+
} else if (0x2f === ch && 0x2a === src.charCodeAt(at + 1)) { // /*
|
|
109
|
+
at += 2
|
|
110
|
+
while (at < length &&
|
|
111
|
+
!(0x2a === src.charCodeAt(at) && 0x2f === src.charCodeAt(at + 1))) {
|
|
112
|
+
at++
|
|
113
|
+
}
|
|
114
|
+
at += 2
|
|
115
|
+
start = true
|
|
116
|
+
} else if (start && (0x22 === ch || 0x27 === ch || 0x60 === ch)) {
|
|
117
|
+
// " or ' or `
|
|
118
|
+
at++
|
|
119
|
+
while (at < length && src.charCodeAt(at) !== ch) {
|
|
120
|
+
at += 0x5c === src.charCodeAt(at) ? 2 : 1 // a backslash escapes one
|
|
121
|
+
}
|
|
122
|
+
at++
|
|
123
|
+
start = true
|
|
124
|
+
} else if (start && isWord(ch)) {
|
|
125
|
+
const from = at
|
|
126
|
+
while (at < length && isWord(src.charCodeAt(at))) at++
|
|
127
|
+
start = KEYWORDS.has(src.slice(from, at))
|
|
128
|
+
} else {
|
|
129
|
+
at++
|
|
130
|
+
start = false
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return deepest
|
|
134
|
+
}
|
package/src/proto.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { GrammarSpec } from '@tabnas/parser'
|
|
|
13
13
|
import compiledGrammar from './proto-grammar.json'
|
|
14
14
|
import { buildFile } from './build-descriptor'
|
|
15
15
|
import { recordAggregate } from './aggregate'
|
|
16
|
+
import { MAX_NESTING_DEPTH, preflight } from './preflight'
|
|
16
17
|
import {
|
|
17
18
|
ProtoVersion, declaredVersion, resolveVersion,
|
|
18
19
|
} from './detect-version'
|
|
@@ -74,8 +75,12 @@ function toDescriptor(cst: any, options?: Partial<ProtoOptions>): FileDescriptor
|
|
|
74
75
|
// Convenience: parse a `.proto` source string to a FileDescriptorProto in
|
|
75
76
|
// one call. Builds a fresh engine each time; for repeated parsing reuse an
|
|
76
77
|
// engine via `const j = new Tabnas().use(Proto)` and call
|
|
77
|
-
// `toDescriptor(j.parse(src), opts)
|
|
78
|
+
// `toDescriptor(j.parse(src), opts)`, running `preflight(src)` first.
|
|
79
|
+
//
|
|
80
|
+
// A document nesting deeper than MAX_NESTING_DEPTH is refused before the
|
|
81
|
+
// engine runs: see ./preflight.ts.
|
|
78
82
|
function parse(src: string, options?: Partial<ProtoOptions>): FileDescriptorProto {
|
|
83
|
+
preflight(src)
|
|
79
84
|
const tn = new Tabnas({ rewind: { history: 8192 } })
|
|
80
85
|
Proto(tn, options)
|
|
81
86
|
return toDescriptor(tn.parse(src), options)
|
|
@@ -84,8 +89,10 @@ function parse(src: string, options?: Partial<ProtoOptions>): FileDescriptorProt
|
|
|
84
89
|
// VERSION is this package's version. It MUST equal package.json "version":
|
|
85
90
|
// the release orchestrator rewrites both, and test/version.test.ts fails the
|
|
86
91
|
// build if they drift. Mirrors `const VERSION` in go/proto.go.
|
|
87
|
-
const VERSION = '0.6.
|
|
92
|
+
const VERSION = '0.6.8'
|
|
88
93
|
|
|
89
|
-
export { Proto, parse, toDescriptor, VERSION }
|
|
94
|
+
export { Proto, parse, toDescriptor, preflight, MAX_NESTING_DEPTH, VERSION }
|
|
90
95
|
export type { ProtoVersion }
|
|
91
96
|
export * from './descriptor'
|
|
97
|
+
export { translate } from './translate'
|
|
98
|
+
export type { TranslationPart, TranslationParts } from './translate'
|