Repository: oppia/oppia

Issue:
Implement new linter checks
This project is aimed at writing linters for the checks below. (Note that, per [@Hudda's audit](https://github.com/oppia/oppia/pull/9337#discussion_r432441499) in May 2020, all checks in this issue would need custom extensions to be developed.) The general procedure to write such linters is as follows: 
- Look at the sample code in the linked issue for the affected area.
- Think about character by character, word by word, or line by line what would convince you that there is an error.
- If it is in a Python file, check whether there is already a built-in pylint rule that can detect this. If not, you'll need to write a Pylint extension..

- See [wiki page](https://github.com/oppia/oppia/wiki/How-to-Write-Lint-Checks) for information on writing lint checks.

- See [this page](http://pylint.pycqa.org/en/latest/how_tos/custom_checkers.html) for information on writing pylint extension.

- Run `python -m scripts.run_backend_tests --test_target=scripts.linters.pylint_extensions_test` for testing pylint extension against your tests.

A few general guidelines while writing such tests:
- Try to look at the existing checks to see how others have approached the issue.
- Look to see if there are existing lint checks out there that can be modified.

**To get started, ask to be assigned to any unclaimed task. Tasks are claimed by adding the username of the claimer to the end of the task, e.g. `@example`.**

---

### Python
- [x] Enforce multiples of 4 indentation in docstring (#8165) 
- [x] Check for proper comment indentation (#6422)
- [x] Add check for proper Args and Returns style, including typeinfo (#6095) @Hudda 
- [x] Add check to multiline expressions ensuring line break after '(' (#6495)  @Hudda 
- [x] Check for args-name for a non-keyword argument (#6067) @Hudda 
- [x] Add check to ensure that pylint pragmas are used to disable any rule for a single line (#5477) @Hudda 
- [x] Add lint check to ensure that there is an empty line between imports and fileoverview (#7830) - @donosco98 
- [x] Add lint check to ensure that there is one blank newline below each class docstring. @Hudda 
- [x] Detect variables that are declared but never used. @Hudda 
- [x] Forbid usage of assertRaises; require assertRaisesRegexp instead. @Hudda 
- [x] Enforce that PEP8 naming convention is followed. Please check the comment [here](https://github.com/oppia/oppia/pull/8868#issuecomment-602290760) to understand the use case. @sajalasati
- [x] Check that functions with `test_only` in their names are only used in `_test.py` files. @anmol1point0
- [x] Add check for excessive new lines between function definition in a class. ([comment](https://github.com/oppia/oppia/pull/9304/files/b35713eb31ef5a5fd9b88f4802562f879ce17b58#r425518951) [another comment](https://github.com/oppia/oppia/pull/10084#discussion_r462137785))
- [x] Add check for newline above Args, Raises and Returns.
- [x] Forbid use of "if x != None" (prefer "if x is not None" instead) (#11108) @michaelw54
- [x] Add a lint check to ensure that there is a single space between `if`, `elif`, `while` and bracket (#10486) @EricZLou 
- [x] Add a lint check to ensure that the type info of *args should be `list(*)`.
- [x] Add a lint check to ensure newline after the `Args:`, `Returns:`, `Raises:`, `Yields:` in docstrings. (@MegaMind77-coder found that the check already happens via some other method - see [this](https://github.com/oppia/oppia/issues/8423#issuecomment-751359033) comment)
- [ ] Add a lint check to ensure that there are no blank lines below the function definition. @deadex-ng 
- [ ] Add a lint check to prevents usage of string concatenation, and promotes string interpolation instead. (This is a coding style guideline of the Oppia codebase - see 4th bullet point [here](https://github.com/oppia/oppia/wiki/Coding-style-guide#python))  @Helper2020 
- [ ] All docstrings must end with a full stop. (Inspect if we already have this check or not - if we have it then [here](https://github.com/oppia/oppia/pull/11910#discussion_r574517696) it was not caught, and if we don't have it then find a way to enable/implement it) 
- [ ] Check to enforce use of Args, Returns and Raises in the docstring. (see explanation [here](https://github.com/oppia/oppia/issues/8423#issuecomment-747669734); Depends on #11776 to be completed first)
- [ ] Every comment should have a single space after it (so, no "#pylint: ...") (Note we already have a single line comment checker, but not sure why [this](https://github.com/oppia/oppia/blob/28ad1734b070617fff999c1093d3b6fd5aad670a/scripts/linters/pylint_extensions.py#L1660) check isn't catching it) 
- [x] Prohibit usage of trailing comments (except for pylint pragmas) - ([comment](https://github.com/oppia/oppia/pull/12471#discussion_r610089664)) @sajalasati (PR #13681)

**Every new JS/TS lint check will be implemented in `scripts/linters/custom_eslint_checks/rule/` directory if there is no eslint plugin available for that purpose.**
### JS/TS
- [x] Add a check to ensure that multi-line expressions in parenthesis are broken down after "(" in JavaScript files (#6047) @Hudda 
- [x] Check for unused directive/service dependencies (#5611) @Hudda 
- [x] Improve reachability of sorted imports (#6513) @Hudda
- [x] Check for eslint-disable statements (#6446) @Hudda 
- [x] Lint check for test files (#5879) @Hudda 
- [x] Detect variables that are defined but never used (https://github.com/oppia/oppia/pull/9202#discussion_r418997633). @Hudda 
- [x] Forbid use of innerHTML due to security issues (see https://github.com/oppia/oppia/pull/8755/files#diff-53372eb49ce2e739c250a7b1c1e98f6dR43 for an example)
- [x] Check for unused imports (see https://github.com/oppia/oppia/pull/8658#discussion_r390046450)
- [x] There should be no space before "function" (see https://github.com/oppia/oppia/pull/8682#discussion_r392562066) @Hudda 
- [x] Catch missing space after semicolon: https://github.com/oppia/oppia/pull/8738#discussion_r403828123 @Hudda 
- [x] Add lint check to ensure use of newline after '(' in a multiline function.
- [ ] Check for unused args in method signatures and remove them (or keep them but prefix with "unused", e.g. for changeDict [here](https://github.com/oppia/oppia/blob/204a287ad47ae6ee3338c045386afb9621d8f6ed/core/templates/domain/topic/topic-update.service.ts#L188)) @kizjkre
- [ ] Add lint check to ensure require(…) statements are alphabetized (#6748) @luccasparoni
- [x] Add eslint check to ensure space between `type` and `:` [e.g: `const variableName:<space>TYPE`] ([comment](https://github.com/oppia/oppia/pull/11640#discussion_r554280229).) @YBCS (#12222)
- [ ] Add a lint check to ensure there is exactly 1 blank line between each test case. (See [comment](https://github.com/oppia/oppia/pull/11863#discussion_r571403485) for ref)
- [ ] All $http calls should be in backend-api files and prohibited elsewhere (#8039)
- [ ] Add lint check for 2-space indentation in class body [#8402 comment] ([ref](https://github.com/eslint/eslint/issues/11485#issuecomment-470256179))(https://github.com/oppia/oppia/pull/8519#discussion_r410513440)
- [ ] Add lint check to ensure that functions are called with the right number of args (e.g. we should catch cases where foo() is called as `foo(a)`).
- [ ] Add lint check to ensure that functions with "testOnly" in their names are only called from "spec" files. @rodemore
- [ ] Add lint check to remove unnecessary require from the codebase ([comment](https://github.com/oppia/oppia/pull/9826#discussion_r453566011))
- [x] Add check to ensure that eslint pragma are used to disable any rule for a single line. ([comment](https://github.com/oppia/oppia/pull/9919#discussion_r457824768)) #10497
- [ ] Add eslint check for spacing in the curly brackets. See(#8128 for more details) @Mayank-gaur
- [ ] Add lint check to ensure that all async functions' names end with "Async", and that all functions that end with "Async" are tagged as async.
- [ ] Add lint check to ensure that all private functions/variables names start with `_`, and that all functions/variables that start with `_` are tagged as `private` (in TypeScript). @shubhamcodez
- [ ] Prevent spaces between typecasts and variable names (see e.g.  https://github.com/oppia/oppia/pull/11410/files/7d872eeb1a42fc23215cac2e296e468ab8bbae88..cdba3fe9653ac2e0c8a4e81be6e4dd0da2bfe65f#diff-3c4de5677e4c7d7363f327115b622002d2522a474d07ed19422d3810b15b9ec9R136)
- [ ] Add a lint check to break after '(', not '='. https://github.com/oppia/oppia/pull/12944#discussion_r645953549
- [ ] Add a lint check to indent by 2 spaces if you break to a newline. https://github.com/oppia/oppia/pull/12944#discussion_r645953549

### E2E tests (Not available -- Reserved for GSoC)
- [x] #13215 @AdityaDubey0 
- [x] Not using await for .first(), .last(), or .get(i) calls on ElementArrayFinder objects @AdityaDubey0 
- [x] Making sure all root element selectors use HTML classes that start with "protractor-test-", e.g. element(by.css('.protractor-test-parent-element')).element(by.css('.class-of-element-you-cannot-change')) @AdityaDubey0 
- [x] Keep element selectors at the tops of files. Where selectors cannot be defined at the top of the file, define locators at the top.
- [x] Do not define element selectors in any files outside of `protractor_utils/` or `extensions/`
- [x] Only select elements using `by.css(...)`

### CSS
- [x] Check for alphabetized list in CSS (#8171 ) @Hudda
- [x] Prohibit inline styling (#6212) @Hudda
- [x] Restrict use of px with 0. ([comment](https://github.com/oppia/oppia/pull/9247#discussion_r422107929)) (#12285) @pawan9116 (#12285)
- [x] Use 3 character hexadecimal notation for color where possible. - [See an example here](https://google.github.io/styleguide/htmlcssguide.html#Hexadecimal_Notation) - (#12282) @tanishq67


### HTML
- [x] Check for space before and after = ([comment](https://github.com/oppia/oppia/pull/9247#discussion_r422119497)) @aasiffaizal (#12225)

### CODEOWNERS
- [x] Ensure that CODEOWNERS lines do not have any comments after the line. [comment](https://github.com/oppia/oppia/pull/10275/files#r468935202) - (#11835)
- [ ] Have codeowners lint check saying if a line is completely superseded (and therefore redundant)

### Other
- [x] Add check to ensure that all lines in skip_files in app.yaml reference valid files in the repository (see this [line](https://github.com/oppia/oppia/blob/a77d4889cf56eeab6af85ede9336aa6ab2c1fc48/app_dev.yaml#L291)). @Hudda 
- [x] Add newline check at end of file (#8252) @Hudda 
- [x] Add lint checks to ensure that there are valid spaces and newlines (#7301) @Hudda 
- [x] Lint checks for webpack config (#7414) @Hudda 
- [x] Check that all TODO comments start with capital letter (#7182) @Hudda
- [x] Check for proper comment style for Python and JS files (#6163) @Hudda
- [x] Ensure that every file (of any type) ends with exactly one newline character. @Hudda 


Discussion:
ok, so I have a silly question... Which file has the linter in it? I am trying to find it to look at previously executed code
@Too-Far, for python files the Linters are in `scripts/pylint_extensions.py`
Thank you so much @donosco98 !
Hey there. I am a newcomer and would like to contribute to this project.
@aureliesami20 sure, take a look at https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia to get started. Also, which linter would you like to implement?
Hello! I would like to take on "Enforce multiples of 4 indentation in docstring"
Thanks @kevinlee12, I will thoroughly read the document.

I can start with ```Detect variables that are declared but never used.```, but first I have a proposition.
While investigating on linters for this project, I came across [coala](https://coala.io) which is a very nice tool that helps to write a single configuration file all our linters (a bunch of linters are supported). Basically a single configuration file can be written for our several languages using the same syntax. I think it would be particularly useful for this project.

What do you guys think about using it ?
cc @brianrodri 
Hi @aureliesami20! Introducing a new tool will need research and a detailed design doc before we can consider using it, so I'd suggest implementing the check first to get a feel for how Oppia's codebase handles linting right now before trying to add any new tools.
Assigned @avanderp to `Enforce multiples of 4 indentation in docstring` per our discussion
hey i am interested on working in this project. can anyone tell me how to get start testing these lint checks. thanks

@ashu9999 which check would you like to implement and did you get a chance to read our getting started [guide](https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia)?
yes i am connected to community from past 2 months. can you tell how can i start working on this project like the relevant files and some examples if they are  in codebase


@ashu9999 this file is a good place to start: https://github.com/oppia/oppia/blob/develop/scripts/pre_commit_linter.py
why are most of the issues marked closed?? Is there any relevant work to do in this project?? I would appreciate any help
The issues were all consolidated into this issue, so they were closed. All lint checks that aren’t checked are still open. 

> On Feb 28, 2020, at 4:06 AM, Ashutosh Kumar <notifications@github.com> wrote:
> 
> ﻿
> why are most of the issues marked closed?? Is there any relevant work to do in this project?? I would appreciate any help
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub, or unsubscribe.

I would like to work on the issue: Add a check to ensure that multi-line expressions in parenthesis are broken down after "(" in JavaScript files. Thanks
@sudiptapradhan1181 I assigned you to `Add a check to ensure that multi-line expressions in parenthesis are broken down after "(" in JavaScript files`!
HI 
I am Ashutosh, an Outreachy Applicant and want to contribute to this project
Can anyone guide me Where to start?? 

Hi @lelouchB, welcome to Oppia. Can you please follow the steps [here](https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia#setting-things-up) to get started? Thanks!
Hi! can I take this, "Add lint check to ensure that there is one blank newline below each class docstring."
@amey-kudari I've assigned you to `Add lint check to ensure that there is one blank newline below each class docstring`!
Hey, I wanted to work on  Lint check for test files (#5879)

@lelouchB I've assigned you!
Hi, I am new to oppia, I would like to work upon "Detect variables that are declared but never used".
Hi @apsychogirl, please follow the instructions in our [guide](https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia#setting-things-up) to get started. Which linter task would you like to do?
@kevinlee12 I would like to take up "Detect variables that are declared but never used" from Python section if it isn't assigned.
@apsychogirl, you got it!
Hello, can I work on "Check for proper comment indentation (#6422)"?
@ishitarb5 I've assigned you!
Hi there! Can I work on 'Detect variables that are defined but never used.' (from the JS section) ?
Hi @sori3006, we'll need you to complete our [contributor's guide](https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia#setting-things-up) before assigning, PTAL!
Hi, @kevinlee12 , I would like to take up `Add newline check at end of file` in others section.
Hi @kevinlee12, 
I would like to work on `Add lint check to ensure require(…) statements are alphabetized (#6748)` in JS\TS section. Could you please assign it to me. Thanks.
Hi @brianrodri , done! Please let me know if I can still take up said task. Thanks!
@Lokesh2703 @sdhiman99 @sori3006 Go ahead, I've assigned you!
Hi @avanderp any updates on this issue?
Hey @sori3006 any updates? Let us know if you need any help.
@sori3006 how is your lint check coming along?
hi, i would like to work on "Add check to multiline expressions ensuring line break after '(' "
Hi, @anmol1point0 I've assigned you.
hey @Hudda ,The issue says closed and also I can see the check is already available in the codebase for the assigned lint.
path - line 167 scripts/linters/pylint_extensions.py
please take a look.
Assigned @anmol1point0 to `Check that functions with test_only in their names are only used in _test.py files.` per email
Hello, I would like to work on "Add check for excessive new lines between function definition in a class."
On second thought, it already seems like there is a check "ExcessiveEmptyLinesChecker" which serves this purpose in "pylint_extensions.py" for spacing between function definitions in the general case (not specifically in a class). If no further modification is required, can I work on "Forbid usage of assertRaises; require assertRaisesRegexp" instead?
@DubeySandeep could you answer the question below from @anmol1point0 (from Gitter)?

> I have a doubt regarding the issue "Check that functions with test_only in their names are only used in _test.py files."
where should I write my lint, pylint_extension.py if yes,
Do I need to write testcase for my lint in pylint_extension_test.py?
Actually I think @Hudda is the person to ask for both questions above (for @Eblanken and @anmol1point0), since he's now the main point of contact for lint checks. I've removed the issue label pointing to @DubeySandeep. 

@Hudda could you PTAL? Thanks!

/cc @bansalnitish 
> @DubeySandeep could you answer the question below from @anmol1point0 (from Gitter)?
> 
> > I have a doubt regarding the issue "Check that functions with test_only in their names are only used in _test.py files."
> > where should I write my lint, pylint_extension.py if yes,
> > Do I need to write testcase for my lint in pylint_extension_test.py?

@anmol1point0 yes, you need to write testcase in the pylint_extension_test.py
> On second thought, it already seems like there is a check "ExcessiveEmptyLinesChecker" which serves this purpose in "pylint_extensions.py" for spacing between function definitions in the general case (not specifically in a class). If no further modification is required, can I work on "Forbid usage of assertRaises; require assertRaisesRegexp" instead?

@Eblanken You are assigned.
@Hudda 
I have a doubt in "Check that functions with test_only in their names are only used in _test.py files.", 
how to get the name of the file, I can get the content of the file by read_from_node, I can't find the resources for this particular doubt anywhere.

i have a solution, I can pass the name of the file as an argument, but i highly doubt this approach.
@anmol1point0 I think it is better to have this check in `python_linter.py` file instead of `pylint_extension`. Because it will be easy to get filename there. @kevinlee12 what do you think?
Agreed
hi, i would like to work on
"Check to enforce use of Args, Returns and Raises in the docstring."
Can I please be assigned ` Add check for newline above Args, Raises and Returns.` - the last one?
@anmol1point0 I've assigned you.
@sajalasati This check is already implemented. Can you pick another one?
@Hudda What about `Enforce that PEP8 naming convention is followed`? Can I take that?

Resolving patch:
diff --git a/scripts/linters/python_linter.py b/scripts/linters/python_linter.py
--- a/scripts/linters/python_linter.py
+++ b/scripts/linters/python_linter.py
@@ -117,6 +117,50 @@ def _check_import_order(self):
                 summary_messages.append(summary_message)
         return summary_messages
 
+    def _check_non_test_files(self):
+        """This function is used to check that function
+           with test_only in their names are in test files.
+        """
+        if self.verbose_mode_enabled:
+            python_utils.PRINT('Starting function defintion checks')
+            python_utils.PRINT('----------------------------------------')
+        summary_messages = []
+        files_to_check = self.py_filepaths
+        with linter_utils.redirect_stdout(sys.stdout):
+            failed = False
+            for filepath in files_to_check:
+                if filepath.endswith('_test.py'):
+                    continue
+                for line_num, line in enumerate(FILE_CACHE.readlines(
+                        filepath)):
+                    line = line.strip()
+                    words = line.split()
+                    if len(words) < 2:
+                        continue
+                    ind1 = words[0].startswith('def')
+                    ind2 = words[1].startswith('test_only')
+                    if ind1 and ind2:
+                        summary_message = (
+                            '%s --> Line %s: Please do not use \'test_only\' '
+                            'in the non-test file.' % (filepath, line_num + 1))
+                        python_utils.PRINT(summary_message)
+                        summary_messages.append(summary_message)
+                        failed = True
+
+            if failed:
+                summary_message = (
+                    '%s Function defintion checks failed,'
+                    'see affect files above.'
+                    % (linter_utils.MESSAGE_TYPE_FAILED))
+            else:
+                summary_message = (
+                    '%s Function definition checks passed'
+                    % (linter_utils.MESSAGE_TYPE_SUCCESS))
+
+            python_utils.PRINT(summary_message)
+            summary_messages.append(summary_message)
+        return summary_messages
+
     def _check_that_all_jobs_are_listed_in_the_job_registry_file(self):
         """This function is used to check that all the one-off and audit jobs
         are registered in jobs_registry.py file.
@@ -246,7 +290,10 @@ def perform_all_lint_checks(self):
         import_order_check_message = self._check_import_order()
         job_registry_check_message = (
             self._check_that_all_jobs_are_listed_in_the_job_registry_file())
-        return import_order_check_message + job_registry_check_message
+        test_function_check_message = self._check_non_test_files()
+        all_messages = import_order_check_message + job_registry_check_message
+        all_messages = all_messages + test_function_check_message
+        return all_messages
 
 
 class ThirdPartyPythonLintChecksManager(python_utils.OBJECT):


Test patch: