rolldown-plugin-angularjs-annotate 0.1.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/LICENSE +22 -0
- package/NOTICE +16 -0
- package/README.md +128 -0
- package/index.d.ts +59 -0
- package/index.js +103 -0
- package/package.json +80 -0
- package/src/annotate.d.ts +3 -0
- package/src/annotate.js +1687 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gilles Piou
|
|
4
|
+
Copyright (c) 2013-2016 Olov Lassus <olov.lassus@gmail.com>
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
This project includes an independent ESTree/MagicString adaptation of matching
|
|
2
|
+
behavior and tests from:
|
|
3
|
+
|
|
4
|
+
- babel-plugin-angularjs-annotate by schmod
|
|
5
|
+
https://github.com/schmod/babel-plugin-angularjs-annotate
|
|
6
|
+
- ng-annotate by Olov Lassus and contributors
|
|
7
|
+
https://github.com/olov/ng-annotate
|
|
8
|
+
|
|
9
|
+
The original ng-annotate copyright is:
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2013-2016 Olov Lassus <olov.lassus@gmail.com>
|
|
12
|
+
|
|
13
|
+
Both upstream projects are distributed under the MIT License. The upstream
|
|
14
|
+
copyright is retained in this project's LICENSE. The development test suite
|
|
15
|
+
loads the published babel-plugin-angularjs-annotate@0.10.0 fixtures directly;
|
|
16
|
+
those fixture files are not included in this package's published files.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# rolldown-plugin-angularjs-annotate
|
|
2
|
+
|
|
3
|
+
AngularJS dependency-injection annotations for Rolldown, without a Babel
|
|
4
|
+
transform layer.
|
|
5
|
+
|
|
6
|
+
This package ports the supported annotation behavior of
|
|
7
|
+
`babel-plugin-angularjs-annotate@0.10.0` to Rolldown. Its complete active corpus
|
|
8
|
+
of 131 fixtures across 11 suites is checked in the upstream project's enabled
|
|
9
|
+
original-source and Babel-transpiled ES5 modes, including context-sensitive
|
|
10
|
+
negative cases and `explicitOnly`.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Requires Node.js `>=22.12.0` and Rolldown `^1.2.0`.
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install --save-dev rolldown-plugin-angularjs-annotate
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const angularjsAnnotate = require('rolldown-plugin-angularjs-annotate');
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
plugins: [angularjsAnnotate()]
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The plugin uses Rolldown's filtered transform hook and native AST/MagicString
|
|
31
|
+
metadata when available. It runs in the `post` phase so framework plugins can
|
|
32
|
+
turn virtual modules, such as Vue single-file component scripts, into
|
|
33
|
+
JavaScript first.
|
|
34
|
+
|
|
35
|
+
This is a Rolldown plugin. Its package metadata deliberately marks Rollup as
|
|
36
|
+
incompatible rather than claiming support for an untested fallback host.
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
angularjsAnnotate({
|
|
42
|
+
include: ['**/src/**/*.js', '**/src/**/*.ts'],
|
|
43
|
+
exclude: '**/*.spec.js',
|
|
44
|
+
explicitOnly: false,
|
|
45
|
+
regexp: '^app(?:\\..+)?$'
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Option | Meaning |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `include` | String glob, `RegExp`, or array of either. Defaults to JavaScript/TypeScript IDs and modules Rolldown identifies as JavaScript/TypeScript, including framework-generated virtual modules. |
|
|
52
|
+
| `exclude` | String glob, `RegExp`, or array of either. Defaults to `node_modules` and Rolldown's runtime module. |
|
|
53
|
+
| `explicitOnly` | When `true`, only `@ngInject`, `ngInject`, and their no-inject counterparts are considered. |
|
|
54
|
+
| `regexp` | Restricts source expressions accepted as implicit module receivers, such as `app` or `require("app-module")`. Explicit `angular.module(...)` chains remain recognized. The compatibility default accepts identifier and dotted-property forms. |
|
|
55
|
+
|
|
56
|
+
Use `regexp: '^$'` to disable implicit receiver matching while retaining explicit
|
|
57
|
+
`angular.module(...)` chains.
|
|
58
|
+
|
|
59
|
+
## Supported annotations
|
|
60
|
+
|
|
61
|
+
Explicit annotation supports:
|
|
62
|
+
|
|
63
|
+
- `@ngInject` and `@ngNoInject` line, block, and JSDoc comments
|
|
64
|
+
- `"ngInject"` and `"ngNoInject"` directive prologues
|
|
65
|
+
- `ngInject(value)` and `ngNoInject(value)` wrappers
|
|
66
|
+
- functions, arrows, classes, constructors, assignments, references, exports,
|
|
67
|
+
object properties, and recursively annotated object literals
|
|
68
|
+
|
|
69
|
+
Implicit matching supports:
|
|
70
|
+
|
|
71
|
+
- long and short AngularJS module registrations and chains
|
|
72
|
+
- controllers, services, factories, filters, directives, providers,
|
|
73
|
+
decorators, animations, components, config/run blocks, invoke, and store
|
|
74
|
+
- component controllers/templates and directive definition objects
|
|
75
|
+
- provider `$get` declarations and reference following
|
|
76
|
+
- `$provide`, `$injector`, `$controllerProvider`, route/UI-Router providers,
|
|
77
|
+
HTTP interceptors, UI Bootstrap modals, and Angular Material overlays
|
|
78
|
+
- lexical binding resolution, shadowing, hoisting, aliases, and direct IIFEs
|
|
79
|
+
|
|
80
|
+
Existing inline arrays, `$inject` assignments, and static class `$inject`
|
|
81
|
+
fields are preserved. Unsupported destructured parameter lists are left
|
|
82
|
+
unchanged instead of producing positionally incorrect annotations. Ambiguous
|
|
83
|
+
mutable references and object methods that rely on `super` are also left
|
|
84
|
+
unchanged rather than receiving a transformation that could alter runtime
|
|
85
|
+
semantics.
|
|
86
|
+
|
|
87
|
+
## Low-level API
|
|
88
|
+
|
|
89
|
+
Pipelines sharing an AST and MagicString instance can call the core directly:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
const { annotate } = require('rolldown-plugin-angularjs-annotate');
|
|
93
|
+
|
|
94
|
+
annotate(program, code, magicString, {
|
|
95
|
+
comments,
|
|
96
|
+
explicitOnly: false
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The `comments` array is optional; the core has a source-based fallback for
|
|
101
|
+
native Rolldown ASTs. Public TypeScript declarations cover both the plugin and
|
|
102
|
+
the low-level API. `onWarn` receives a message and, when available, a diagnostic
|
|
103
|
+
with zero-based `start` and `end` source offsets. The plugin wrapper forwards
|
|
104
|
+
these as structured Rolldown warnings with the module ID and source position;
|
|
105
|
+
annotation and parser failures similarly use Rolldown's structured error API.
|
|
106
|
+
|
|
107
|
+
## Verification
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
npm run check
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The suite includes 508 semantic comparisons against the upstream compatibility
|
|
114
|
+
corpus, safety regressions, a minified AngularJS `strictDi` bootstrap, source
|
|
115
|
+
maps, filters, TypeScript/Vue module IDs, framework-generated modules with
|
|
116
|
+
non-script IDs, package consumer types, and real Rolldown builds with native
|
|
117
|
+
MagicString both disabled and enabled. Babel is a development-only fixture
|
|
118
|
+
preprocessor for the ES5 compatibility lane; published transforms use
|
|
119
|
+
Rolldown's parser and do not add a Babel transform layer.
|
|
120
|
+
|
|
121
|
+
## AI disclosure
|
|
122
|
+
|
|
123
|
+
Developed entirely with OpenAI Codex (GPT-5.6 Sol, using Ultra and Extra High
|
|
124
|
+
reasoning effort).
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT. See [NOTICE](NOTICE) for upstream attribution.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type MagicString from 'magic-string';
|
|
2
|
+
import type { Plugin, RolldownMagicString } from 'rolldown';
|
|
3
|
+
import type { ESTree } from 'rolldown/utils';
|
|
4
|
+
|
|
5
|
+
declare function angularjsAnnotate(
|
|
6
|
+
options?: angularjsAnnotate.AngularJSAnnotateOptions,
|
|
7
|
+
): Plugin;
|
|
8
|
+
|
|
9
|
+
declare namespace angularjsAnnotate {
|
|
10
|
+
type FilterPattern = string | RegExp | readonly (string | RegExp)[];
|
|
11
|
+
|
|
12
|
+
interface AngularJSAnnotateOptions {
|
|
13
|
+
/** Module IDs transformed by the plugin. */
|
|
14
|
+
include?: FilterPattern;
|
|
15
|
+
/** Module IDs omitted by the plugin. */
|
|
16
|
+
exclude?: FilterPattern;
|
|
17
|
+
/** Disable all implicit AngularJS pattern matching. */
|
|
18
|
+
explicitOnly?: boolean;
|
|
19
|
+
/** Restrict source expressions accepted as implicit AngularJS module receivers. */
|
|
20
|
+
regexp?: string | RegExp;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ParserComment {
|
|
24
|
+
/** Accepted for compatibility with parser comment objects; annotation only uses the range and value. */
|
|
25
|
+
type?: string;
|
|
26
|
+
value: string;
|
|
27
|
+
start: number;
|
|
28
|
+
end: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AnnotateDiagnostic {
|
|
32
|
+
/** Stable diagnostic identifier suitable for filtering build logs. */
|
|
33
|
+
code?: string;
|
|
34
|
+
/** Zero-based source offsets when the diagnostic is tied to syntax. */
|
|
35
|
+
start?: number;
|
|
36
|
+
end?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface AnnotateOptions {
|
|
40
|
+
comments?: readonly ParserComment[] | null;
|
|
41
|
+
explicitOnly?: boolean;
|
|
42
|
+
regexp?: string | RegExp;
|
|
43
|
+
/** Receives the message and, when available, its source span. */
|
|
44
|
+
onWarn?: (message: string, diagnostic?: AnnotateDiagnostic) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function annotate(
|
|
48
|
+
program: ESTree.Program,
|
|
49
|
+
code: string,
|
|
50
|
+
magicString: MagicString | RolldownMagicString,
|
|
51
|
+
options?: AnnotateOptions,
|
|
52
|
+
): void;
|
|
53
|
+
|
|
54
|
+
const angularjsAnnotate: (
|
|
55
|
+
options?: AngularJSAnnotateOptions,
|
|
56
|
+
) => Plugin;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export = angularjsAnnotate;
|
package/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MagicString = require('magic-string');
|
|
4
|
+
const { parseSync } = require('rolldown/utils');
|
|
5
|
+
const annotate = require('./src/annotate');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_EXCLUDE = [/(?:^|[/\\])node_modules[/\\]/, /\0rolldown[/\\]runtime\.js$/];
|
|
8
|
+
|
|
9
|
+
function angularjsAnnotate(options = {}) {
|
|
10
|
+
const { include, exclude = DEFAULT_EXCLUDE } = options;
|
|
11
|
+
const useModuleTypeFilter = include === undefined;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
name: 'rolldown-plugin-angularjs-annotate',
|
|
15
|
+
enforce: 'post',
|
|
16
|
+
transform: {
|
|
17
|
+
filter: useModuleTypeFilter ? {
|
|
18
|
+
id: { exclude },
|
|
19
|
+
moduleType: ['js', 'jsx', 'ts', 'tsx'],
|
|
20
|
+
} : { id: { include, exclude } },
|
|
21
|
+
handler(code, id, meta = {}) {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = meta.ast ? { program: meta.ast, comments: null, errors: [] } : parseSync(id, code, {
|
|
25
|
+
lang: language(id, meta.moduleType),
|
|
26
|
+
sourceType: 'unambiguous',
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
reportError(this, error, id);
|
|
30
|
+
}
|
|
31
|
+
if (parsed.errors.length) reportError(this, parserError(parsed.errors[0], id), id);
|
|
32
|
+
|
|
33
|
+
const magicString = meta.magicString || new MagicString(code);
|
|
34
|
+
try {
|
|
35
|
+
annotate(parsed.program, code, magicString, {
|
|
36
|
+
comments: parsed.comments,
|
|
37
|
+
explicitOnly: options.explicitOnly,
|
|
38
|
+
regexp: options.regexp,
|
|
39
|
+
onWarn: (message, diagnostic) => {
|
|
40
|
+
if (typeof this?.warn !== 'function') return;
|
|
41
|
+
this.warn({
|
|
42
|
+
message,
|
|
43
|
+
id,
|
|
44
|
+
pluginCode: diagnostic?.code || 'ANNOTATION_MISMATCH',
|
|
45
|
+
}, diagnosticPosition(diagnostic));
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
reportError(this, error, id);
|
|
50
|
+
}
|
|
51
|
+
if (!magicString.hasChanged()) return;
|
|
52
|
+
|
|
53
|
+
return meta.magicString ? { code: magicString } : {
|
|
54
|
+
code: magicString.toString(),
|
|
55
|
+
map: magicString.generateMap({ hires: 'boundary', includeContent: true, source: id }),
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function language(id, moduleType) {
|
|
63
|
+
if (isJavaScriptModuleType(moduleType)) return moduleType;
|
|
64
|
+
const match = /(?:\.([cm]?[jt]sx?)(?:$|\?)|(?:^|[?&])lang\.([jt]sx?)(?:&|$))/.exec(id);
|
|
65
|
+
const extension = match?.[1] || match?.[2];
|
|
66
|
+
if (extension?.endsWith('tsx')) return 'tsx';
|
|
67
|
+
if (extension?.endsWith('ts')) return 'ts';
|
|
68
|
+
if (extension?.endsWith('jsx')) return 'jsx';
|
|
69
|
+
return 'js';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isJavaScriptModuleType(moduleType) {
|
|
73
|
+
return moduleType === 'js' || moduleType === 'jsx' || moduleType === 'ts' || moduleType === 'tsx';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parserError(diagnostic, id) {
|
|
77
|
+
const message = diagnostic?.message || diagnostic?.labels?.[0]?.message || String(diagnostic);
|
|
78
|
+
const error = new SyntaxError(message);
|
|
79
|
+
error.cause = diagnostic;
|
|
80
|
+
error.id = id;
|
|
81
|
+
error.pluginCode = 'PARSE_ERROR';
|
|
82
|
+
const position = diagnosticPosition(diagnostic);
|
|
83
|
+
if (position !== undefined) error.start = position;
|
|
84
|
+
return error;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function reportError(context, value, id) {
|
|
88
|
+
const error = value instanceof Error ? value : new Error(String(value));
|
|
89
|
+
if (error.id === undefined) error.id = id;
|
|
90
|
+
if (error.pluginCode === undefined) error.pluginCode = error.code || 'ANNOTATION_ERROR';
|
|
91
|
+
if (typeof context?.error === 'function') context.error(error, diagnosticPosition(error));
|
|
92
|
+
if (!error.message.startsWith(`${id}: `)) error.message = `${id}: ${error.message}`;
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function diagnosticPosition(diagnostic) {
|
|
97
|
+
const position = diagnostic?.start ?? diagnostic?.labels?.[0]?.start;
|
|
98
|
+
return Number.isInteger(position) && position >= 0 ? position : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = angularjsAnnotate;
|
|
102
|
+
module.exports.annotate = annotate;
|
|
103
|
+
module.exports.angularjsAnnotate = angularjsAnnotate;
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rolldown-plugin-angularjs-annotate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AngularJS dependency injection annotations for Rolldown without a Babel transform layer",
|
|
5
|
+
"author": "Gilles Piou",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/pioug/rolldown-plugin-angularjs-annotate.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/pioug/rolldown-plugin-angularjs-annotate#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/pioug/rolldown-plugin-angularjs-annotate/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"main": "index.js",
|
|
17
|
+
"types": "index.d.ts",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./index.d.ts",
|
|
22
|
+
"require": "./index.js",
|
|
23
|
+
"default": "./index.js"
|
|
24
|
+
},
|
|
25
|
+
"./core": {
|
|
26
|
+
"types": "./src/annotate.d.ts",
|
|
27
|
+
"require": "./src/annotate.js",
|
|
28
|
+
"default": "./src/annotate.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.js",
|
|
34
|
+
"index.d.ts",
|
|
35
|
+
"NOTICE",
|
|
36
|
+
"src"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "node --test",
|
|
40
|
+
"test:parity": "node --test test/parity.test.js",
|
|
41
|
+
"test:unit": "node --test test/annotate.test.js test/angular-aliases.test.js test/binding-resolution.test.js test/markers.test.js test/nested-targets.test.js test/plugin.test.js test/typescript.test.js",
|
|
42
|
+
"typecheck": "tsc -p type-tests/tsconfig.json",
|
|
43
|
+
"lint:package": "publint",
|
|
44
|
+
"verify:package-contents": "node scripts/verify-package-contents.js",
|
|
45
|
+
"check": "npm run typecheck && npm test && npm run lint:package",
|
|
46
|
+
"prepublishOnly": "npm run check && npm run verify:package-contents"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"angularjs",
|
|
50
|
+
"dependency-injection",
|
|
51
|
+
"ng-annotate",
|
|
52
|
+
"rolldown",
|
|
53
|
+
"rolldown-plugin"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22.12.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"magic-string": "^0.30.21"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"rolldown": "^1.2.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@babel/core": "^7.29.7",
|
|
66
|
+
"@babel/preset-env": "^7.29.7",
|
|
67
|
+
"angular": "1.8.3",
|
|
68
|
+
"babel-plugin-angularjs-annotate": "0.10.0",
|
|
69
|
+
"publint": "^0.3.21",
|
|
70
|
+
"rolldown": "^1.2.0",
|
|
71
|
+
"typescript": "^5.9.3"
|
|
72
|
+
},
|
|
73
|
+
"compatiblePackages": {
|
|
74
|
+
"schemaVersion": 1,
|
|
75
|
+
"rollup": {
|
|
76
|
+
"type": "incompatible",
|
|
77
|
+
"reason": "Targets and tests Rolldown only and depends on Rolldown parser and plugin APIs"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|