Repository: eslint/eslint
Pull request #8086 (2017-02-16 22:22:42)

Docs: Add TSC meeting quorum requirement
Adding a note about TSC meetings requiring a quorum of more than half of the TSC being present at meetings in order to vote on any agenda items.

**What is the purpose of this pull request? (put an "X" next to item)**

[x] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

**What changes did you make? (Give an overview)**

Updated governance documentation to clarify that TSC meetings do not allow voting on issues unless a quorum of members are present.

This is not meant to be a process change: the TSC has been following this unwritten rule since it was formed. However, it was not documented on the governance page.

**Is there anything you'd like reviewers to focus on?**

Yes: I want to make sure that what I've written is actually what's being followed. My goal is not to propose a process change, but rather to document what seems to be the existing process. Let me know if this should go on the TSC Agenda to get the appropriate oversight.

eslintbot: LGTM
mention-bot: @platinumazure, thanks for your PR! By analyzing the history of the files in this pull request, we identified @nzakas to be a potential reviewer.

Patch:
diff --git a/docs/maintainer-guide/governance.md b/docs/maintainer-guide/governance.md
index eea276e8b..098f11598 100644
--- a/docs/maintainer-guide/governance.md
+++ b/docs/maintainer-guide/governance.md
@@ -122,6 +122,10 @@ members of the TSC. TSC members can add any items they like to the
 agenda at the beginning of each meeting. The moderator and the TSC
 cannot veto or remove items.
 
+No binding votes on TSC agenda items can take place without a quorum of
+TSC members present in the meeting. Quorum is achieved when more than
+half of the TSC members are present.
+
 The TSC may invite persons or representatives from certain projects to
 participate in a non-voting capacity.
 
diff --git a/lib/token-store/index.js b/lib/token-store/index.js
index b74126b90..bd523fdaa 100644
--- a/lib/token-store/index.js
+++ b/lib/token-store/index.js
@@ -45,7 +45,7 @@ const PUBLIC_METHODS = Object.freeze([
  * Creates the map from locations to indices in `tokens`.
  *
  * The first/last location of tokens is mapped to the index of the token.
- * The first/last location of comments is mapped to the index of the previous token of each comment.
+ * The first/last location of comments is mapped to the index of the next token of each comment.
  *
  * @param {Token[]} tokens - The array of tokens.
  * @param {Comment[]} comments - The array of comments.
diff --git a/lib/token-store/utils.js b/lib/token-store/utils.js
index 2dea7636f..2f6cff1c7 100644
--- a/lib/token-store/utils.js
+++ b/lib/token-store/utils.js
@@ -61,7 +61,15 @@ exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) {
         return indexMap[startLoc];
     }
     if ((startLoc - 1) in indexMap) {
-        return indexMap[startLoc - 1] + 1;
+        const index = indexMap[startLoc - 1];
+        const token = (index >= 0 && index < tokens.length) ? tokens[index] : null;
+
+        // For the map of "comment's location -> token's index", it points the next token of a comment.
+        // In that case, +1 is unnecessary.
+        if (token && token.range[0] >= startLoc) {
+            return index;
+        }
+        return index + 1;
     }
     return 0;
 };
@@ -81,7 +89,15 @@ exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) {
         return indexMap[endLoc] - 1;
     }
     if ((endLoc - 1) in indexMap) {
-        return indexMap[endLoc - 1];
+        const index = indexMap[endLoc - 1];
+        const token = (index >= 0 && index < tokens.length) ? tokens[index] : null;
+
+        // For the map of "comment's location -> token's index", it points the next token of a comment.
+        // In that case, -1 is necessary.
+        if (token && token.range[1] > endLoc) {
+            return index - 1;
+        }
+        return index;
     }
     return tokens.length - 1;
 };


Test patch:
diff --git a/tests/lib/token-store.js b/tests/lib/token-store.js
index 0d2c6d81f..063beda45 100644
--- a/tests/lib/token-store.js
+++ b/tests/lib/token-store.js
@@ -271,6 +271,33 @@ describe("TokenStore", () => {
             );
         });
 
+        it("should retrieve the previous node if the comment at the end of source code is specified.", () => {
+            const code = "a + b /*comment*/";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const token = tokenStore.getTokenBefore(ast.comments[0]);
+
+            assert.strictEqual(token.value, "b");
+        });
+
+        it("should retrieve the previous comment if the first token is specified.", () => {
+            const code = "/*comment*/ a + b";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const token = tokenStore.getTokenBefore(ast.tokens[0], { includeComments: true });
+
+            assert.strictEqual(token.value, "comment");
+        });
+
+        it("should retrieve null if the first comment is specified.", () => {
+            const code = "/*comment*/ a + b";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const token = tokenStore.getTokenBefore(ast.comments[0], { includeComments: true });
+
+            assert.strictEqual(token, null);
+        });
+
     });
 
     describe("when calling getTokensAfter", () => {
@@ -417,6 +444,33 @@ describe("TokenStore", () => {
             );
         });
 
+        it("should retrieve the next node if the comment at the first of source code is specified.", () => {
+            const code = "/*comment*/ a + b";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const token = tokenStore.getTokenAfter(ast.comments[0]);
+
+            assert.strictEqual(token.value, "a");
+        });
+
+        it("should retrieve the next comment if the last token is specified.", () => {
+            const code = "a + b /*comment*/";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const token = tokenStore.getTokenAfter(ast.tokens[2], { includeComments: true });
+
+            assert.strictEqual(token.value, "comment");
+        });
+
+        it("should retrieve null if the last comment is specified.", () => {
+            const code = "a + b /*comment*/";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const token = tokenStore.getTokenAfter(ast.comments[0], { includeComments: true });
+
+            assert.strictEqual(token, null);
+        });
+
     });
 
     describe("when calling getFirstTokens", () => {
@@ -567,6 +621,35 @@ describe("TokenStore", () => {
             );
         });
 
+        it("should retrieve the first comment if the comment is at the last of nodes", () => {
+            const code = "a + b\n/*comment*/ c + d";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+
+            // Actually, the first of nodes is always tokens, not comments.
+            // But I think this test case is needed for completeness.
+            const token = tokenStore.getFirstToken(
+                { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] },
+                { includeComments: true }
+            );
+
+            assert.strictEqual(token.value, "comment");
+        });
+
+        it("should retrieve the first token (without includeComments option) if the comment is at the last of nodes", () => {
+            const code = "a + b\n/*comment*/ c + d";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+
+            // Actually, the first of nodes is always tokens, not comments.
+            // But I think this test case is needed for completeness.
+            const token = tokenStore.getFirstToken(
+                { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] }
+            );
+
+            assert.strictEqual(token.value, "c");
+        });
+
     });
 
     describe("when calling getLastTokens", () => {
@@ -721,6 +804,35 @@ describe("TokenStore", () => {
             );
         });
 
+        it("should retrieve the last comment if the comment is at the last of nodes", () => {
+            const code = "a + b /*comment*/\nc + d";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+
+            // Actually, the last of nodes is always tokens, not comments.
+            // But I think this test case is needed for completeness.
+            const token = tokenStore.getLastToken(
+                { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] },
+                { includeComments: true }
+            );
+
+            assert.strictEqual(token.value, "comment");
+        });
+
+        it("should retrieve the last token (without includeComments option) if the comment is at the last of nodes", () => {
+            const code = "a + b /*comment*/\nc + d";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+
+            // Actually, the last of nodes is always tokens, not comments.
+            // But I think this test case is needed for completeness.
+            const token = tokenStore.getLastToken(
+                { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] }
+            );
+
+            assert.strictEqual(token.value, "b");
+        });
+
     });
 
     describe("when calling getFirstTokensBetween", () => {
@@ -1066,4 +1178,79 @@ describe("TokenStore", () => {
 
     });
 
+    describe("when calling getFirstToken & getTokenAfter", () => {
+        it("should retrieve all tokens and comments in the node", () => {
+            const code = "(function(a, /*b,*/ c){})";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const tokens = [];
+            let token = tokenStore.getFirstToken(ast);
+
+            while (token) {
+                tokens.push(token);
+                token = tokenStore.getTokenAfter(token, { includeComments: true });
+            }
+
+            check(
+                tokens,
+                ["(", "function", "(", "a", ",", "b,", "c", ")", "{", "}", ")"]
+            );
+        });
+
+        it("should retrieve all tokens and comments in the node (no spaces)", () => {
+            const code = "(function(a,/*b,*/c){})";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const tokens = [];
+            let token = tokenStore.getFirstToken(ast);
+
+            while (token) {
+                tokens.push(token);
+                token = tokenStore.getTokenAfter(token, { includeComments: true });
+            }
+
+            check(
+                tokens,
+                ["(", "function", "(", "a", ",", "b,", "c", ")", "{", "}", ")"]
+            );
+        });
+    });
+
+    describe("when calling getLastToken & getTokenBefore", () => {
+        it("should retrieve all tokens and comments in the node", () => {
+            const code = "(function(a, /*b,*/ c){})";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const tokens = [];
+            let token = tokenStore.getLastToken(ast);
+
+            while (token) {
+                tokens.push(token);
+                token = tokenStore.getTokenBefore(token, { includeComments: true });
+            }
+
+            check(
+                tokens.reverse(),
+                ["(", "function", "(", "a", ",", "b,", "c", ")", "{", "}", ")"]
+            );
+        });
+
+        it("should retrieve all tokens and comments in the node (no spaces)", () => {
+            const code = "(function(a,/*b,*/c){})";
+            const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true });
+            const tokenStore = new TokenStore(ast.tokens, ast.comments);
+            const tokens = [];
+            let token = tokenStore.getLastToken(ast);
+
+            while (token) {
+                tokens.push(token);
+                token = tokenStore.getTokenBefore(token, { includeComments: true });
+            }
+
+            check(
+                tokens.reverse(),
+                ["(", "function", "(", "a", ",", "b,", "c", ")", "{", "}", ")"]
+            );
+        });
+    });
 });