acorn-private-methods
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -78,3 +78,4 @@ { | ||
"error", | ||
"declaration" | ||
"declaration", | ||
{ "allowArrowFunctions": true } | ||
], | ||
@@ -81,0 +82,0 @@ "function-paren-newline": "off", |
@@ -0,1 +1,7 @@ | ||
## 0.2.0 (2019-09-14) | ||
* Update to new acorn 6 interface | ||
* Change license to MIT | ||
* Don't allow direct super() calls in private methods | ||
## 0.1.1 (2018-02-09) | ||
@@ -2,0 +8,0 @@ |
126
index.js
"use strict" | ||
module.exports = require("./inject")(require("acorn")) | ||
const acorn = require("acorn") | ||
const tt = acorn.tokTypes | ||
const TokenType = acorn.TokenType | ||
const privateNameToken = new TokenType("privateName") | ||
function parsePrivateName() { | ||
const node = this.startNode() | ||
node.name = this.value | ||
this.next() | ||
this.finishNode(node, "PrivateName") | ||
if (this.options.allowReserved == "never") this.checkUnreserved(node) | ||
return node | ||
} | ||
module.exports = function(Parser) { | ||
return class extends Parser { | ||
// Parse # token | ||
getTokenFromCode(code) { | ||
if (code === 35) { | ||
++this.pos | ||
const word = this.readWord1() | ||
return this.finishToken(privateNameToken, word) | ||
} | ||
return super.getTokenFromCode(code) | ||
} | ||
// Manage stacks and check for undeclared private names | ||
parseClass(node, isStatement) { | ||
this._privateBoundNamesStack = this._privateBoundNamesStack || [] | ||
const privateBoundNames = Object.create(this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1] || null) | ||
this._privateBoundNamesStack.push(privateBoundNames) | ||
this._unresolvedPrivateNamesStack = this._unresolvedPrivateNamesStack || [] | ||
const unresolvedPrivateNames = Object.create(null) | ||
this._unresolvedPrivateNamesStack.push(unresolvedPrivateNames) | ||
const _return = super.parseClass(node, isStatement) | ||
this._privateBoundNamesStack.pop() | ||
this._unresolvedPrivateNamesStack.pop() | ||
if (!this._unresolvedPrivateNamesStack.length) { | ||
const names = Object.keys(unresolvedPrivateNames) | ||
if (names.length) { | ||
names.sort((n1, n2) => unresolvedPrivateNames[n1] - unresolvedPrivateNames[n2]) | ||
this.raise(unresolvedPrivateNames[names[0]], "Usage of undeclared private name") | ||
} | ||
} else Object.assign(this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1], unresolvedPrivateNames) | ||
return _return | ||
} | ||
// Parse private methods | ||
parseClassElement() { | ||
const oldInClassMemberName = this._inClassMemberName | ||
this._inClassMemberName = true | ||
const result = super.parseClassElement() | ||
this._inClassMemberName = oldInClassMemberName | ||
return result | ||
} | ||
parsePropertyName(prop) { | ||
const isPrivate = this.options.ecmaVersion >= 8 && this._inClassMemberName && this.type == privateNameToken | ||
this._inClassMemberName = false | ||
if (!isPrivate) return super.parsePropertyName(prop) | ||
prop.computed = false | ||
prop.key = parsePrivateName.call(this) | ||
if (prop.key.name == "constructor") this.raise(prop.start, "Classes may not have a private method named constructor") | ||
const privateBoundNames = this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1] | ||
if (Object.prototype.hasOwnProperty.call(privateBoundNames, prop.key.name) && !(prop.kind === "get" && privateBoundNames[prop.key.name] === "set") && !(prop.kind === "set" && privateBoundNames[prop.key.name] === "get")) this.raise(prop.start, "Duplicate private element") | ||
privateBoundNames[prop.key.name] = prop.kind | ||
delete this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1][prop.key.name] | ||
prop.key.type = "PrivateName" | ||
return prop.key | ||
} | ||
parseClassMethod(method, isGenerator, isAsync) { | ||
const oldInPrivateClassMethod = this._inPrivateClassMethod | ||
this._inPrivateClassMethod = method.key.type == "PrivateName" | ||
const ret = super.parseClassMethod(method, isGenerator, isAsync) | ||
this._inPrivateClassMethod = oldInPrivateClassMethod | ||
return ret | ||
} | ||
// Parse private element access | ||
parseSubscripts(base, startPos, startLoc, noCalls) { | ||
for (let computed; ;) { | ||
if ((computed = this.eat(tt.bracketL)) || this.eat(tt.dot)) { | ||
let node = this.startNodeAt(startPos, startLoc) | ||
node.object = base | ||
if (computed) { | ||
node.property = this.parseExpression() | ||
} else if (this.type == privateNameToken) { | ||
node.property = parsePrivateName.call(this) | ||
if (!this._privateBoundNamesStack.length || !this._privateBoundNamesStack[this._privateBoundNamesStack.length - 1][node.property.name]) { | ||
this._unresolvedPrivateNamesStack[this._unresolvedPrivateNamesStack.length - 1][node.property.name] = node.property.start | ||
} | ||
} else { | ||
node.property = this.parseIdent(true) | ||
} | ||
node.computed = Boolean(computed) | ||
if (computed) this.expect(tt.bracketR) | ||
base = this.finishNode(node, "MemberExpression") | ||
} else { | ||
return super.parseSubscripts(base, startPos, startLoc, noCalls) | ||
} | ||
} | ||
} | ||
// Prohibit delete of private class elements | ||
parseMaybeUnary(refDestructuringErrors, sawUnary) { | ||
const _return = super.parseMaybeUnary(refDestructuringErrors, sawUnary) | ||
if (_return.operator == "delete") { | ||
if (_return.argument.type == "MemberExpression" && _return.argument.property.type == "PrivateName") { | ||
this.raise(_return.start, "Private elements may not be deleted") | ||
} | ||
} | ||
return _return | ||
} | ||
// Prohibit direct super in private methods | ||
parseExprAtom(refDestructuringErrors) { | ||
const atom = super.parseExprAtom(refDestructuringErrors) | ||
if (this._inPrivateClassMethod && atom.type == "Super" && this.type == tt.parenL) this.raise(atom.start, "A class method that is not a constructor may not contain a direct super") | ||
return atom | ||
} | ||
} | ||
} |
@@ -15,3 +15,3 @@ { | ||
}, | ||
"license": "AGPL-3.0", | ||
"license": "MIT", | ||
"scripts": { | ||
@@ -22,13 +22,14 @@ "test": "mocha", | ||
}, | ||
"dependencies": { | ||
"acorn": "^5.4.0" | ||
"peerDependencies": { | ||
"acorn": "^6.0.0" | ||
}, | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"devDependencies": { | ||
"eslint": "^4.13.1", | ||
"eslint-plugin-node": "^6.0.0", | ||
"mocha": "^5.0.0", | ||
"test262": "git+https://github.com/tc39/test262.git#18c1e799a01cc976695983b61e225ce7959bdd91", | ||
"test262-parser-runner": "^0.3.1" | ||
"acorn": "^6.0.0", | ||
"eslint": "^5.5.0", | ||
"eslint-plugin-node": "^7.0.1", | ||
"mocha": "^5.2.0", | ||
"test262": "git+https://github.com/tc39/test262.git#e286bfa00086226f781a3ed4e0a6295634b8ed11", | ||
"test262-parser-runner": "^0.4.0" | ||
} | ||
} |
@@ -11,24 +11,12 @@ # Private methods and getter/setters support for Acorn | ||
You can use this module directly in order to get an Acorn instance with the plugin installed: | ||
This module provides a plugin that can be used to extend the Acorn `Parser` class: | ||
```javascript | ||
var acorn = require('acorn-private-methods'); | ||
const {Parser} = require('acorn'); | ||
const privateMethods = require('acorn-private-methods'); | ||
Parser.extend(privateMethods).parse('class X { #a() {} }'); | ||
``` | ||
Or you can use `inject.js` for injecting the plugin into your own version of Acorn like this: | ||
```javascript | ||
var acorn = require('acorn-private-methods/inject')(require('./custom-acorn')); | ||
``` | ||
Then, use the `plugins` option to enable the plugiin: | ||
```javascript | ||
var ast = acorn.parse(code, { | ||
plugins: { privateMethods: true } | ||
}); | ||
``` | ||
## License | ||
This plugin is released under the [GNU Affero General Public License](./LICENSE). | ||
This plugin is released under an [MIT License](./LICENSE). |
@@ -6,3 +6,5 @@ "use strict" | ||
const run = require("test262-parser-runner") | ||
const parse = require(".").parse | ||
const acorn = require("acorn") | ||
const privateMethods = require(".") | ||
const Parser = acorn.Parser.extend(privateMethods) | ||
@@ -12,15 +14,11 @@ const unsupportedFeatures = [ | ||
"class-fields-private", | ||
"class-fields-public", | ||
"optional-catch-binding", | ||
"regexp-lookbehind", | ||
"regexp-named-groups", | ||
"regexp-unicode-property-escapes" | ||
"class-fields-public" | ||
] | ||
const implementedFeatures = [ | ||
// See https://github.com/tc39/test262/issues/1343 | ||
"class-methods-private" | ||
] | ||
run( | ||
(content, options) => parse(content, {sourceType: options.sourceType, ecmaVersion: 9, plugins: { privateMethods: true }}), | ||
(content, options) => Parser.parse(content, {sourceType: options.sourceType, ecmaVersion: 9}), | ||
{ | ||
@@ -27,0 +25,0 @@ testsDirectory: path.dirname(require.resolve("test262/package.json")), |
192
test/test.js
"use strict" | ||
const assert = require("assert") | ||
const acorn = require("..") | ||
const acorn = require("acorn") | ||
const privateMethods = require("..") | ||
const Parser = acorn.Parser.extend(privateMethods) | ||
function test(text, expectedResult, additionalOptions) { | ||
it(text, function () { | ||
const result = acorn.parse(text, Object.assign({ ecmaVersion: 9, plugins: { privateMethods: true } }, additionalOptions)) | ||
if (expectedResult) { | ||
assert.deepEqual(result.body[0], expectedResult) | ||
} | ||
const result = Parser.parse(text, Object.assign({ ecmaVersion: 9 }, additionalOptions)) | ||
if (expectedResult) assert.deepStrictEqual(result.body[0], expectedResult) | ||
}) | ||
} | ||
function testFail(text, expectedResult, additionalOptions) { | ||
function testFail(text, expectedError, additionalOptions) { | ||
it(text, function () { | ||
let failed = false | ||
try { | ||
acorn.parse(text, Object.assign({ ecmaVersion: 9, plugins: { privateMethods: true } }, additionalOptions)) | ||
Parser.parse(text, Object.assign({ ecmaVersion: 9, plugins: { privateMethods: true } }, additionalOptions)) | ||
} catch (e) { | ||
assert.equal(e.message, expectedResult) | ||
assert.strictEqual(e.message, expectedError) | ||
failed = true | ||
@@ -27,2 +27,3 @@ } | ||
const newNode = (start, props) => Object.assign(new acorn.Node({options: {}}, start), props) | ||
describe("acorn-private-methods", function () { | ||
@@ -41,2 +42,4 @@ test("class A { a() { this.#a }; #a() {} }") | ||
testFail("class A{ # a() {}}", "Unexpected token (1:11)") | ||
testFail("class C{ #method() { super(); } };", "A class method that is not a constructor may not contain a direct super (1:21)") | ||
test("class C{ #method() { super.y(); } };") | ||
@@ -46,62 +49,52 @@ const classes = [ | ||
const body = getBody(10) | ||
return { | ||
return newNode(0, { | ||
type: "ClassDeclaration", | ||
start: 0, | ||
end: body.end + 2, | ||
id: { | ||
id: newNode(6, { | ||
type: "Identifier", | ||
start: 6, | ||
end: 7, | ||
name: "A" | ||
}, | ||
}), | ||
superClass: null, | ||
body: { | ||
body: newNode(8, { | ||
type: "ClassBody", | ||
start: 8, | ||
end: body.end + 2, | ||
body: [body] | ||
} | ||
} | ||
}) | ||
}) | ||
} }, | ||
{ text: "class A { %s; }", ast: getBody => { | ||
const body = getBody(10) | ||
return { | ||
return newNode(0, { | ||
type: "ClassDeclaration", | ||
start: 0, | ||
end: body.end + 3, | ||
id: { | ||
id: newNode(6, { | ||
type: "Identifier", | ||
start: 6, | ||
end: 7, | ||
name: "A" | ||
}, | ||
}), | ||
superClass: null, | ||
body: { | ||
body: newNode(8, { | ||
type: "ClassBody", | ||
start: 8, | ||
end: body.end + 3, | ||
body: [body] | ||
} | ||
} | ||
}) | ||
}) | ||
} }, | ||
{ text: "class A { %s; #y() {} }", ast: getBody => { | ||
const body = getBody(10) | ||
return { | ||
return newNode(0, { | ||
type: "ClassDeclaration", | ||
start: 0, | ||
end: body.end + 11, | ||
id: { | ||
id: newNode(6, { | ||
type: "Identifier", | ||
start: 6, | ||
end: 7, | ||
name: "A" | ||
}, | ||
}), | ||
superClass: null, | ||
body: { | ||
body: newNode(8, { | ||
type: "ClassBody", | ||
start: 8, | ||
end: body.end + 11, | ||
body: [body, { | ||
body: [body, newNode(body.end + 2, { | ||
type: "MethodDefinition", | ||
start: body.end + 2, | ||
end: body.end + 9, | ||
@@ -111,11 +104,9 @@ kind: "method", | ||
computed: false, | ||
key: { | ||
key: newNode(body.end + 2, { | ||
type: "PrivateName", | ||
start: body.end + 2, | ||
end: body.end + 4, | ||
name: "y" | ||
}, | ||
value: { | ||
}), | ||
value: newNode(body.end + 4, { | ||
type: "FunctionExpression", | ||
start: body.end + 4, | ||
end: body.end + 9, | ||
@@ -127,33 +118,28 @@ id: null, | ||
params: [], | ||
body: { | ||
body: newNode(body.end + 7, { | ||
type: "BlockStatement", | ||
start: body.end + 7, | ||
end: body.end + 9, | ||
body: [] | ||
} | ||
} | ||
} ] | ||
} | ||
} | ||
}) | ||
}) | ||
}) ] | ||
}) | ||
}) | ||
} }, | ||
{ text: "class A { %s;a() {} }", ast: getBody => { | ||
const body = getBody(10) | ||
return { | ||
return newNode(0, { | ||
type: "ClassDeclaration", | ||
start: 0, | ||
end: body.end + 9, | ||
id: { | ||
id: newNode(6, { | ||
type: "Identifier", | ||
start: 6, | ||
end: 7, | ||
name: "A" | ||
}, | ||
}), | ||
superClass: null, | ||
body: { | ||
body: newNode(8, { | ||
type: "ClassBody", | ||
start: 8, | ||
end: body.end + 9, | ||
body: [ body, { | ||
body: [ body, newNode(body.end + 1, { | ||
type: "MethodDefinition", | ||
start: body.end + 1, | ||
end: body.end + 7, | ||
@@ -163,11 +149,9 @@ kind: "method", | ||
computed: false, | ||
key: { | ||
key: newNode(body.end + 1, { | ||
type: "Identifier", | ||
start: body.end + 1, | ||
end: body.end + 2, | ||
name: "a" | ||
}, | ||
value: { | ||
}), | ||
value: newNode(body.end + 2, { | ||
type: "FunctionExpression", | ||
start: body.end + 2, | ||
end: body.end + 7, | ||
@@ -179,35 +163,30 @@ id: null, | ||
params: [], | ||
body: { | ||
body: newNode(body.end + 5, { | ||
type: "BlockStatement", | ||
start: body.end + 5, | ||
end: body.end + 7, | ||
body: [] | ||
} | ||
} | ||
} ] | ||
} | ||
} | ||
}) | ||
}) | ||
}) ] | ||
}) | ||
}) | ||
} }, | ||
{ text: "class A { %s\na() {} }", ast: getBody => { | ||
const body = getBody(10) | ||
return { | ||
return newNode(0, { | ||
type: "ClassDeclaration", | ||
start: 0, | ||
end: body.end + 9, | ||
id: { | ||
id: newNode(6, { | ||
type: "Identifier", | ||
start: 6, | ||
end: 7, | ||
name: "A" | ||
}, | ||
}), | ||
superClass: null, | ||
body: { | ||
body: newNode(8, { | ||
type: "ClassBody", | ||
start: 8, | ||
end: body.end + 9, | ||
body: [ | ||
body, | ||
{ | ||
newNode(body.end + 1, { | ||
type: "MethodDefinition", | ||
start: body.end + 1, | ||
end: body.end + 7, | ||
@@ -217,11 +196,9 @@ kind: "method", | ||
computed: false, | ||
key: { | ||
key: newNode(body.end + 1, { | ||
type: "Identifier", | ||
start: body.end + 1, | ||
end: body.end + 2, | ||
name: "a" | ||
}, | ||
value: { | ||
}), | ||
value: newNode(body.end + 2, { | ||
type: "FunctionExpression", | ||
start: body.end + 2, | ||
end: body.end + 7, | ||
@@ -233,13 +210,12 @@ id: null, | ||
params: [], | ||
body: { | ||
body: newNode(body.end + 5, { | ||
type: "BlockStatement", | ||
start: body.end + 5, | ||
end: body.end + 7, | ||
body: [] | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
}) | ||
] | ||
} | ||
} | ||
}) | ||
}) | ||
} }, | ||
@@ -249,26 +225,22 @@ ]; | ||
[ | ||
{ body: "#x() {}", passes: true, ast: start => ({ | ||
{ body: "#x() {}", passes: true, ast: start => newNode(start, { | ||
type: "MethodDefinition", | ||
start: start, | ||
end: start + 7, | ||
computed: false, | ||
key: { | ||
key: newNode(start, { | ||
type: "PrivateName", | ||
start: start, | ||
end: start + 2, | ||
name: "x" | ||
}, | ||
}), | ||
kind: "method", | ||
static: false, | ||
value: { | ||
value: newNode(start + 2, { | ||
type: "FunctionExpression", | ||
start: start + 2, | ||
end: start + 7, | ||
async: false, | ||
body: { | ||
body: newNode(start + 5, { | ||
type: "BlockStatement", | ||
body: [], | ||
start: start + 5, | ||
end: start + 7, | ||
type: "BlockStatement" | ||
}, | ||
}), | ||
expression: false, | ||
@@ -278,28 +250,24 @@ generator: false, | ||
params: [], | ||
} | ||
}) | ||
}) }, | ||
{ body: "get #x() {}", passes: true, ast: start => ({ | ||
{ body: "get #x() {}", passes: true, ast: start => newNode(start, { | ||
type: "MethodDefinition", | ||
start: start, | ||
end: start + 11, | ||
computed: false, | ||
key: { | ||
key: newNode(start + 4, { | ||
type: "PrivateName", | ||
start: start + 4, | ||
end: start + 6, | ||
name: "x" | ||
}, | ||
}), | ||
kind: "get", | ||
static: false, | ||
value: { | ||
value: newNode(start + 6, { | ||
type: "FunctionExpression", | ||
start: start + 6, | ||
end: start + 11, | ||
async: false, | ||
body: { | ||
body: newNode(start + 9, { | ||
body: [], | ||
start: start + 9, | ||
end: start + 11, | ||
type: "BlockStatement" | ||
}, | ||
}), | ||
expression: false, | ||
@@ -309,3 +277,3 @@ generator: false, | ||
params: [], | ||
} | ||
}) | ||
}) }, | ||
@@ -312,0 +280,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Copyleft License
License(Experimental) Copyleft license information was found.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found.
Found 1 instance in 1 package
0
100
26952
6
10
707
22
+ Addedacorn@6.4.2(transitive)
- Removedacorn@^5.4.0
- Removedacorn@5.7.4(transitive)