├── .gitignore ├── .replit ├── .rspec ├── .rubocop.yml ├── .stickler.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── exe └── jscop ├── jscop.gemspec ├── lib ├── classes │ ├── error.rb │ ├── js_parser.rb │ └── line.rb ├── helpers │ └── lint_ware.rb ├── jscop.rb └── jscop │ ├── check_js_files.rb │ ├── class_count.rb │ ├── class_name.rb │ ├── naming_checker.rb │ ├── spacing_checker.rb │ ├── unused_var_checker.rb │ └── version.rb └── spec ├── asset.js ├── class_name_spec.rb ├── js_parser_spec.rb ├── jscop_spec.rb ├── line_spec.rb ├── naming_checker_spec.rb ├── spacing_checker_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | /vendor/**/*/ 10 | 11 | # rspec failure tracking 12 | .rspec_status 13 | *.gem 14 | -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | language = "ruby" 2 | run = "exe/jscop" -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - "spec/spec_helper.rb" 4 | - "lib/jscop/spacing_checker.rb" 5 | - "README.md" 6 | - "Guardfile" 7 | - "Rakefile" 8 | - "vendor/**/*" 9 | - "jscop.gemspec" 10 | 11 | DisplayCopNames: true 12 | 13 | Lint/RaiseException: 14 | Enabled: false 15 | Lint/StructNewOverride: 16 | Enabled: false 17 | Lint/UselessAssignment: 18 | Enabled: false 19 | 20 | Layout/LineLength: 21 | Max: 120 22 | Metrics/MethodLength: 23 | Max: 30 24 | Metrics/AbcSize: 25 | Max: 50 26 | Metrics/ClassLength: 27 | Max: 150 28 | Metrics/BlockLength: 29 | ExcludedMethods: ["describe"] 30 | Max: 30 31 | Metrics/ModuleLength: 32 | Max: 150 33 | Metrics/CyclomaticComplexity: 34 | Max: 20 35 | Metrics/PerceivedComplexity: 36 | Max: 20 37 | Metrics/ParameterLists: 38 | Max: 7 39 | 40 | Style/Documentation: 41 | Enabled: false 42 | Style/ClassAndModuleChildren: 43 | Enabled: false 44 | Style/EachForSimpleLoop: 45 | Enabled: false 46 | Style/AndOr: 47 | Enabled: false 48 | Style/DefWithParentheses: 49 | Enabled: false 50 | Style/FrozenStringLiteralComment: 51 | EnforcedStyle: never 52 | Style/NegatedIf: 53 | Enabled: false 54 | Style/NegatedUnless: 55 | Enabled: false 56 | Style/BlockDelimiters: 57 | Enabled: false 58 | 59 | Style/HashEachMethods: 60 | Enabled: false 61 | Style/HashTransformKeys: 62 | Enabled: false 63 | Style/HashTransformValues: 64 | Enabled: false 65 | 66 | Layout/HashAlignment: 67 | EnforcedColonStyle: key 68 | Layout/ExtraSpacing: 69 | AllowForAlignment: false 70 | Layout/MultilineMethodCallIndentation: 71 | Enabled: true 72 | EnforcedStyle: indented 73 | -------------------------------------------------------------------------------- /.stickler.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | rubocop: 3 | display_cop_names: true 4 | config: "./rubocop.yml" 5 | 6 | files: 7 | ignore: 8 | - "Guardfile" 9 | - "lib/jscop/spacing_checker.rb" 10 | - "Rakefile" 11 | - "node_modules/**/*" 12 | - "spec/spec_helper.rb" 13 | - "vendor/**/*" 14 | - "jscop.gemspec" 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: false 3 | language: ruby 4 | cache: bundler 5 | rvm: 6 | - 2.6.5 7 | before_install: gem install bundler -v 1.17.2 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at ezakaphil@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in jscop.gemspec 6 | gemspec 7 | gem 'colorize', '~> 0.8.1' 8 | gem 'rake', '~> 12.3.3' 9 | gem 'rspec' 10 | gem 'rubocop' 11 | gem 'tty-font', '~> 0.5.0' 12 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | jscop (0.1.8) 5 | colorize (~> 0.8) 6 | tty-font (~> 0.5) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | ast (2.4.1) 12 | colorize (0.8.1) 13 | diff-lcs (1.4.4) 14 | parallel (1.19.2) 15 | parser (2.7.1.4) 16 | ast (~> 2.4.1) 17 | rainbow (3.0.0) 18 | rake (12.3.3) 19 | regexp_parser (1.7.1) 20 | rexml (3.2.4) 21 | rspec (3.9.0) 22 | rspec-core (~> 3.9.0) 23 | rspec-expectations (~> 3.9.0) 24 | rspec-mocks (~> 3.9.0) 25 | rspec-core (3.9.2) 26 | rspec-support (~> 3.9.3) 27 | rspec-expectations (3.9.2) 28 | diff-lcs (>= 1.2.0, < 2.0) 29 | rspec-support (~> 3.9.0) 30 | rspec-mocks (3.9.1) 31 | diff-lcs (>= 1.2.0, < 2.0) 32 | rspec-support (~> 3.9.0) 33 | rspec-support (3.9.3) 34 | rubocop (0.89.1) 35 | parallel (~> 1.10) 36 | parser (>= 2.7.1.1) 37 | rainbow (>= 2.2.2, < 4.0) 38 | regexp_parser (>= 1.7) 39 | rexml 40 | rubocop-ast (>= 0.3.0, < 1.0) 41 | ruby-progressbar (~> 1.7) 42 | unicode-display_width (>= 1.4.0, < 2.0) 43 | rubocop-ast (0.3.0) 44 | parser (>= 2.7.1.4) 45 | ruby-progressbar (1.10.1) 46 | tty-font (0.5.0) 47 | unicode-display_width (1.7.0) 48 | 49 | PLATFORMS 50 | ruby 51 | 52 | DEPENDENCIES 53 | bundler (~> 2.1.0) 54 | colorize (~> 0.8.1) 55 | jscop! 56 | rake (~> 12.3.3) 57 | rspec 58 | rubocop 59 | tty-font (~> 0.5.0) 60 | 61 | BUNDLED WITH 62 | 2.1.4 63 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 codecell solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/jscop.svg)](https://badge.fury.io/rb/jscop) [![Build Status](https://travis-ci.org/codecell/jscop.svg?branch=development)](https://travis-ci.org/codecell/jscop) 2 | 3 | # Jscop 4 | 5 | This is a Linter for checking errors in Javascript code, built with Ruby. 6 | 7 | ## Outline 8 | - [Installation](#Installation) 9 | - [Usage](#Usage) 10 | - [Errors currently detectable by jscop](#ERRORS) 11 | - Variable naming errors 12 | - Class naming errors 13 | - Extra or redundant spacing errors 14 | - Multiple declaration of classes in a file (class count) errors 15 | - Unused variables are detected 16 | - [More about usage](#More-About-Usage) 17 | - [Testing this repo](#Testing-This-Repo) 18 | - [Built With](#Built-With) 19 | - [Contributing](#contributing) 20 | - [Code of conduct](#Code-of-Conduct) 21 | - [Licence](#License) 22 | 23 | ## Installation 24 | 25 | Add this line to your application's Gemfile: 26 | 27 | ```ruby 28 | gem 'jscop' 29 | ``` 30 | 31 | And then execute: 32 | 33 | $ bundle 34 | 35 | Or install it yourself as: 36 | 37 | $ gem install jscop 38 | 39 | ## Usage 40 | - once in desired path in the terminal/console, RUN 41 | 42 | ```ruby 43 | a. jscop # To lint all *.js files in the current $Path. 44 | b. jscop ./**/path_to_file.js # To lint JUST a specific file. 45 | c. jscop foldername # To lint all *.js files in a specific folder. 46 | ``` 47 | 48 | ## ERRORS 49 | The following erros are currently detectable by Jscop: 50 | 51 | ### VARIABLE NAMING ERRORS 52 | - if capital letters or numbers are used to start a variable name, e.g 53 | - `*Bad practice* let 8owngoal = 4 or const Capvar = 9` 54 | - `*Good practice* let owngoal = 4 or const capVar = 9` 55 | 56 | ### CLASS NAME ERRORS 57 | - if class names begin with small letters and/or 58 | - if class names are snake-cased or both e.g 59 | - `*Bad practice* class badclass {} or class Bad-class {}` 60 | - `*Good practice* class GoodClass {} or class Good_Class {}` 61 | - Or if underscores and hyphen are mixed 62 | `*Bad practice* class Bad_class-Name {}` 63 | 64 | ### SPACING ERRORS 65 | - if spaces are found at beginning of lines 66 | - if extra spaces are found around variables e.g 67 | - `*Bad practice* let| | vacuum = 4 or var hollow| | = 9` 68 | - `*Good practice* let vacuum = 4 or const hollow = 9` 69 | 70 | ### CLASS COUNT ERROR 71 | - if more than one class is defined in a module, 72 | 73 | ### UNUSED VARIABLE ERROR 74 | - if a variable (var, let, const) is defined and never used. 75 | - A lot of edge cases are covered here. 76 | 77 | ## More About Usage 78 | Arguments Supported By jscop to Check Javascript Code 79 | - You can pass in file(s) or a folder in any of the following cases in your terminal 80 | - `You can pass in a specific file $Path to test just the file` 81 | - `You can pass in a folder and all javascript files in it will be checked and finally` 82 | - `Everything Js can be linted all at once (both files and folders)` 83 | 84 | ## Testing this Repo 85 | - Once in the $Path you intend to keep this project in the terminal of your machine 86 | - Clone with this command `git clone [repo](https://github.com/codecell/jscop.git)` 87 | - Change directory into the folder `cd jscop` 88 | - Install project dependencies with the command `bundle install` 89 | - You can make the `exe/jscop` file executable with the command `chmod a+x exe/jscop` if permission is not granted 90 | - Ensure you have `rspec` installed, or ensure that `gem rspec` is listed among the gems in the `Gemfile` => then run `bundle install` 91 | - In the console, run `rspec` or `rake` to run all the unit tests written for this project 92 | 93 | ## Built With 94 | - Ruby 95 | - [Rubular](https://rubular.com/) was used to craft and test the regular expressions 96 | - Rubocop linter 97 | - Stickler CI 98 | 99 | ## Contributing 100 | 101 | Bug reports and pull requests are welcome on GitHub at https://github.com/codecell/jscop. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 102 | 103 | ## Code of Conduct 104 | 105 | Everyone interacting in the Jscop project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/codecell/jscop/blob/master/CODE_OF_CONDUCT.md). 106 | 107 | ## Author 108 | 109 | 👤 **Alfred Ezaka** 110 | 111 | - Github: [@codecell](https://github.com/codecell) 112 | - Twitter: [@the_codecell](https://twitter.com/the_codecell) 113 | - Linkedin: [ezaka alfred](https://www.linkedin.com/in/alfrednoble/) 114 | 115 | ## License 116 | 117 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 118 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | 5 | # You can add fixtures and/or initialization code here to make experimenting 6 | # with your gem easier. You can also use a different console, if you like. 7 | 8 | # (If you use this, don't forget to add pry to your Gemfile!) 9 | # require "pry" 10 | # Pry.start 11 | 12 | require 'irb' 13 | IRB.start(__FILE__) 14 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /exe/jscop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'tty-font' 3 | require 'colorize' 4 | 5 | require 'jscop/version' 6 | require_relative '../lib/helpers/lint_ware' 7 | 8 | def show_title 9 | font = TTY::Font.new 10 | font_messge = font.write('Jscop', letter_spacing: 1) 11 | puts font_messge 12 | end 13 | 14 | def print_linter_result(error_bin, fpath) 15 | puts 16 | puts "#{error_bin.length.to_s.yellow} Offense(s) Detected" unless error_bin.empty? 17 | 18 | path_exists = File.exist?(fpath) || Dir.exist?(fpath) 19 | 20 | success_msg = 'As clean as Snow!. No offenses found in the $PATH' 21 | 22 | puts "#{success_msg} #{fpath}".green if error_bin.empty? && path_exists == true 23 | puts 24 | end 25 | 26 | def jscop_init 27 | show_title 28 | 29 | path = ARGV[0] 30 | path = '.' if ARGV.empty? 31 | 32 | error_bin = [] 33 | 34 | js_file_pattern = /(\w|\W)+.js$/ 35 | 36 | if js_file_pattern.match?(path) 37 | LintWare.init_files_linting(error_bin, path) 38 | else 39 | LintWare.init_dir_linting(error_bin, path) 40 | end 41 | 42 | print_linter_result(error_bin, path) 43 | end 44 | 45 | jscop_init 46 | -------------------------------------------------------------------------------- /jscop.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'jscop/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'jscop' 7 | spec.version = Jscop::VERSION 8 | spec.authors = ['Ezaka Alfred'] 9 | spec.email = ['ezakaphil@gmail.com'] 10 | 11 | spec.summary = 'A linter for checking errors in javascript code' 12 | spec.homepage = 'https://github.com/codecell/jscop' 13 | spec.license = 'MIT' 14 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 15 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 16 | end 17 | 18 | spec.bindir = 'exe' 19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.executables << 'jscop' 21 | spec.require_paths = %w[lib spec] 22 | 23 | spec.add_runtime_dependency 'colorize', '~> 0.8' 24 | spec.add_runtime_dependency 'tty-font', '~> 0.5' 25 | 26 | spec.add_development_dependency 'bundler', '~> 2.1.0' 27 | spec.add_development_dependency 'rake', '~> 12.3' 28 | spec.add_development_dependency 'rspec', '~> 3.0' 29 | end 30 | -------------------------------------------------------------------------------- /lib/classes/error.rb: -------------------------------------------------------------------------------- 1 | class Error 2 | attr_accessor :line, :err_type, :file_path, :variable 3 | 4 | # rubocop:disable Lint/UnusedMethodArgument 5 | def initialize(line, err_type, fpath, *variable) 6 | @line = line 7 | @err_type = err_type 8 | @file_path = fpath 9 | end 10 | # rubocop:enable Lint/UnusedMethodArgument 11 | 12 | def print_err(line, type, path, *variable) 13 | error_hash = { 14 | 'VAR_NAMING_ERR': 'VariableName Error: Uppercase|Numbers used to Start a Variable name.', 15 | 'CLASS_NAME_ERR': 'ClassName Error: Ensure classes Begin With Uppercase and is not Snake-Cased.', 16 | 'SPACING_ERR': 'Spacing Error: Redundant Space(s).', 17 | 'CLASS_COUNT_ERR': 'Count Error: Multiple Classes Defined in a Module.', 18 | 'UNUSED_VAR_ERR': "Warning: Unused Variable ** #{variable[0]} ** Detected." 19 | } 20 | 21 | emit_err = lambda { |key| 22 | if type == 'UNUSED_VAR_ERR' && key[0].to_s == type 23 | puts "#{key[1].to_s.yellow} On Line #{line.to_s.yellow} in #{path.to_s.yellow}" if line 24 | puts 25 | elsif type != 'UNUSED_VAR_ERR' && key[0].to_s == type 26 | puts "#{key[1].to_s.red} Detected On Line #{line.to_s.yellow} in #{path.to_s.yellow}" 27 | puts 28 | end 29 | } 30 | 31 | error_hash.each(&emit_err) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/classes/js_parser.rb: -------------------------------------------------------------------------------- 1 | require_relative './line' 2 | 3 | class JsParser 4 | attr_reader :filename, :lines 5 | 6 | def initialize(filename) 7 | @filename = filename 8 | @lines = [] 9 | set_lines 10 | end 11 | 12 | def set_lines 13 | file = File.open(@filename) 14 | config_dirs = File.dirname(@filename).match?(/(node_modules|config|babel|channels|vendor)/) 15 | 16 | files_for_escape = file if config_dirs == true 17 | files_for_escape.to_a && config_dirs 18 | 19 | all_lines = file.readlines.map(&:chomp) 20 | init_lines = lambda { |val, index| 21 | line = Line.new(index + 1, val, @filename) 22 | 23 | @lines << line 24 | } 25 | 26 | all_lines.each_with_index(&init_lines) 27 | file.close 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/classes/line.rb: -------------------------------------------------------------------------------- 1 | class Line 2 | attr_reader :number, :content, :filename 3 | 4 | def initialize(number, content, filename) 5 | @number = number 6 | @content = content 7 | @filename = filename 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/helpers/lint_ware.rb: -------------------------------------------------------------------------------- 1 | require_relative '../jscop/check_js_files' 2 | require_relative '../jscop/spacing_checker' 3 | require_relative '../jscop/naming_checker' 4 | require_relative '../jscop/class_count' 5 | require_relative '../jscop/class_name' 6 | require_relative '../jscop/unused_var_checker' 7 | 8 | require_relative '../classes/js_parser' 9 | 10 | module LintWare 11 | private_class_method def self.start_all(error_bin, given_file) 12 | file = JsParser.new(given_file) 13 | 14 | NamingChecker.check_naming_res(error_bin, file) 15 | SpacingChecker.check_spaces_res(error_bin, file) 16 | ClassCount.class_count_res(error_bin, file) 17 | ClassName.check_class_name_res(error_bin, file) 18 | UnusedVarChecker.check_unused_var_res(error_bin, file) 19 | 20 | error_bin 21 | end 22 | 23 | def self.init_files_linting(error_bin, path) 24 | if CheckJsFiles.find_file(path) 25 | start_all(error_bin, path) 26 | else 27 | puts "No such File as #{path}".yellow 28 | end 29 | end 30 | 31 | def self.init_dir_linting(error_bin, path) 32 | if CheckJsFiles.find_dir(path) 33 | files = CheckJsFiles.seek_js(path) 34 | files.each { |file| start_all(error_bin, file) } && files 35 | else 36 | CheckJsFiles.find_dir(path) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/jscop.rb: -------------------------------------------------------------------------------- 1 | require 'jscop/version' 2 | 3 | module Jscop 4 | class Error < StandardError; end 5 | # Your code goes here... 6 | end 7 | -------------------------------------------------------------------------------- /lib/jscop/check_js_files.rb: -------------------------------------------------------------------------------- 1 | # Locate Js files in defined paths on the Terminal 2 | 3 | module CheckJsFiles 4 | def self.find_file(path) 5 | puts "No such File as #{path}" if !File.exist?(path) 6 | 7 | File.exist?(path) 8 | end 9 | 10 | def self.find_dir(path) 11 | puts "No such Folder as #{path}".yellow unless Dir.exist?(path) 12 | 13 | Dir.exist?(path) 14 | end 15 | 16 | def self.seek_js(*path) 17 | if path[0] 18 | files = Dir["#{path[0]}/**/*.js"] 19 | return files if !files.empty? 20 | 21 | puts "No JS files found in #{path[0]} Path" if files.empty? 22 | else 23 | files = Dir['./**/*.js'] 24 | return files if !files.empty? 25 | 26 | puts 'No JS files found in this Folder' if files.empty? 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/jscop/class_count.rb: -------------------------------------------------------------------------------- 1 | require_relative '../classes/error' 2 | 3 | module ClassCount 4 | def self.class_count_res(error_bin, path) 5 | lines_with_class_def = class_count(path) 6 | error_bin << lines_with_class_def if lines_with_class_def.length > 1 7 | 8 | error_bin 9 | end 10 | 11 | private_class_method def self.raise_err(line, message, path) 12 | error = Error.new(line, message, path) 13 | class_count_err = error.print_err(line, message, path) if error 14 | class_count_err 15 | end 16 | 17 | def self.class_count(file) 18 | pat = /(class)/ 19 | commented_line = %r{^\W+[\/\/]} 20 | 21 | lines_with_class = [] 22 | err_type = 'CLASS_COUNT_ERR' 23 | 24 | file.lines.each { |line| 25 | needed_lines_with_class = pat.match?(line.content) && !line.content.match?(commented_line) 26 | lines_with_class << line.number if needed_lines_with_class 27 | } 28 | raise_err(lines_with_class, err_type, file.filename) if lines_with_class.length > 1 29 | 30 | lines_with_class 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/jscop/class_name.rb: -------------------------------------------------------------------------------- 1 | require_relative '../classes/error' 2 | 3 | module ClassName 4 | def self.check_class_name_res(error_bin, path) 5 | bad_class_lines = check_class_name(path) 6 | bad_class_lines.each { |line| error_bin << line if !bad_class_lines.empty? } 7 | 8 | error_bin 9 | end 10 | 11 | private_class_method def self.raise_err(line, message, path) 12 | error = Error.new(line, message, path) 13 | naming_err = error.print_err(line, message, path) if error 14 | naming_err 15 | end 16 | 17 | def self.bad_class_name(crime) 18 | fes_pat = /[\s]*(class)[\s]*[\d]*[\-]*[a-z]+[\-]*[\w\W]*/ 19 | sec_pat = /[\s]*(class)[\s]*[\p{Alpha}]+[\-]+[\p{Alpha}]+/ 20 | 21 | commented_line = crime.match?(%r{^\W+[\/\/]}) 22 | 23 | !commented_line && (fes_pat.match?(crime) || sec_pat.match?(crime)) 24 | end 25 | 26 | def self.check_class_name(file) 27 | bad_class_named_lines = [] 28 | line_check = lambda { |line| 29 | bad_class_named_lines << line.number if bad_class_name(line.content) 30 | } 31 | file.lines.each(&line_check) 32 | 33 | err_type = 'CLASS_NAME_ERR' 34 | bad_class_named_lines.each { |line| raise_err(line, err_type, file.filename) } 35 | 36 | bad_class_named_lines 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/jscop/naming_checker.rb: -------------------------------------------------------------------------------- 1 | require_relative '../classes/error' 2 | 3 | module NamingChecker 4 | def self.check_naming_res(error_bin, path) 5 | bad_named_lines = check_naming(path) 6 | bad_named_lines.each { |line| error_bin << line if bad_named_lines } 7 | 8 | error_bin 9 | end 10 | 11 | private_class_method def self.raise_err(line, err_type, path_name) 12 | error = Error.new(line, err_type, path_name) 13 | naming_err = error.print_err(line, err_type, path_name) if error 14 | naming_err 15 | end 16 | 17 | def self.bad_var_case(bad_case) 18 | bad_var_start = /(var|let|const|[\s])[\s]*([[:upper:]]{1,}|\d)+(([\w]+[\s][\w]+)|[\w]+)[\s]*[\=][\s]*[\w]*/ 19 | commented_line = bad_case.match?(%r{^\W+[\/\/]}) 20 | 21 | bad_var_start.match?(bad_case) && !commented_line 22 | end 23 | 24 | def self.check_naming(fpath) 25 | bad_lines = [] 26 | check_line = lambda { |line| 27 | line_with_bad_naming = line if bad_var_case(line.content) 28 | bad_lines << line_with_bad_naming.number if line_with_bad_naming 29 | } 30 | err_type = 'VAR_NAMING_ERR' 31 | fpath.lines.each(&check_line) 32 | 33 | bad_lines.each { |line| raise_err(line, err_type, fpath.filename) if !bad_lines.empty? } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/jscop/spacing_checker.rb: -------------------------------------------------------------------------------- 1 | require_relative '../classes/error' 2 | 3 | module SpacingChecker 4 | def self.check_spaces_res(error_bin, path) 5 | bad_spaced_lines = check_spaces(path) 6 | bad_spaced_lines.each { |line| error_bin << line if !bad_spaced_lines.empty? } 7 | 8 | error_bin 9 | end 10 | 11 | private_class_method def self.raise_err(line, message, path) 12 | error = Error.new(line, message, path) 13 | spacing_err = error.print_err(line, message, path) if error 14 | spacing_err 15 | end 16 | 17 | def self.found_spaces(cont) 18 | /(?\w+[\W]*)\s{2,}(?\w+[\W]*)/ =~ cont 19 | vsf = !Regexp.last_match.nil? 20 | 21 | /(?\w+\W*)\s{2,}=\s*(?\w+\W*)/ =~ cont 22 | beq = !Regexp.last_match.nil? 23 | 24 | /(?\w+\W*)\s*=\s{2,}(?\w+\W*)/ =~ cont 25 | aeq = !Regexp.last_match.nil? 26 | 27 | spaced_console = /(console.log)[\s+][\(][\w\W]+[\)]/ 28 | spc = spaced_console.match?(cont) 29 | commented_line = cont.match?(%r{^\W+[\/\/]}) 30 | 31 | !commented_line && (vsf || beq || aeq || spc) 32 | end 33 | 34 | def self.spc_around_fn(cont) 35 | around_funcs = /[\)][\s]{2,}[\{]/.match?(cont) 36 | around_classes = /[\w+\-*]*[\s]{2,}[\{]/.match?(cont) 37 | 38 | commented_line = cont.match?(%r{^\W+[\/\/]}) 39 | 40 | !commented_line && (around_funcs || around_classes) 41 | end 42 | 43 | def self.closing_curly_spacing(cont) 44 | spaced_closing_curly = /[\s]+[\}][\s]*/ 45 | c = spaced_closing_curly.match(cont) 46 | 47 | commented_line = cont.match?(%r{^\W+[\/\/]}) 48 | 49 | c && !commented_line 50 | end 51 | 52 | def self.closed_curly(cont) 53 | cont.match?(/}/) 54 | end 55 | 56 | def self.open_curly(cont) 57 | cont.match?(/{/) 58 | end 59 | 60 | def self.line_beginining_spaces(line) 61 | /^[\s+][\w\W]*/.match?(line) 62 | end 63 | 64 | def self.check_spaces(file) 65 | seen_open = false 66 | counter = 0 67 | lines_with_spaces = [] 68 | 69 | opening_tracker = 0 70 | closing_tracker = 0 71 | 72 | arr = file.lines 73 | err_type = 'SPACING_ERR' 74 | 75 | while counter < arr.length 76 | line = arr[counter] 77 | 78 | seen_open = true if open_curly(line.content) 79 | 80 | opening_tracker += 1 if line.content.match?(/{/) 81 | closing_tracker += 1 if line.content.match?(/}/) && !line.content.match?(/[\}][\s]*[\)]/) 82 | 83 | lines_with_spaces << line.number if line_beginining_spaces(line.content) unless seen_open 84 | opt = (opening_tracker == closing_tracker) 85 | seen_open = false if closed_curly(line.content) && opt 86 | 87 | lines_with_spaces << line.number if closing_curly_spacing(line.content) && opt 88 | lines_with_spaces << line.number if found_spaces(line.content) && !lines_with_spaces.nil? 89 | lines_with_spaces << line.number if spc_around_fn(line.content) 90 | 91 | counter += 1 92 | end 93 | 94 | lines_with_spaces.each { |line| raise_err(line, err_type, file.filename) if !lines_with_spaces.empty? } 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/jscop/unused_var_checker.rb: -------------------------------------------------------------------------------- 1 | require_relative '../classes/error' 2 | 3 | module UnusedVarChecker 4 | def self.check_unused_var_res(error_bin, path) 5 | unused_var = check_unused_var(path) 6 | unused_var.each { |line, _variabl| error_bin << line if !unused_var.empty? } 7 | 8 | error_bin 9 | end 10 | 11 | private_class_method def self.raise_err(line, message, path, variable) 12 | error = Error.new(line, message, path, variable) 13 | unused_var_err = error.print_err(line, message, path, variable) if error 14 | unused_var_err 15 | end 16 | 17 | def self.check_escapable(elem) 18 | escapables = [ 19 | '', 'var', 'let', 'const', 'constructor', 'class', 'super', 'function', 'static', 'console', 20 | 'prototype', 'get', 'set', 'this', 'alert', 'prompt', 'return', 'export', 'import', 'default', 21 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 22 | ] 23 | 24 | escapables.include?(elem) 25 | end 26 | 27 | def self.vars_parentesis(paramz) 28 | /(?[\(])(?[^\{]*[\w\-]+)/ =~ paramz 29 | vars = Regexp.last_match(:rhs) 30 | vars = vars.to_s.split if vars 31 | vars_match_data = vars.to_s.split 32 | 33 | each_var = vars_match_data.collect { |var| 34 | /([\w\-]+)/ =~ var 35 | wanted = Regexp.last_match(0) 36 | wanted = wanted.to_s 37 | wanted 38 | } 39 | each_var[0] if each_var[0] && !each_var.nil? 40 | end 41 | 42 | def self.match_accessd_var(vari) 43 | /(?[\w+\-*]+)[\[](?[\w+\-*]+)/ =~ vari 44 | sqad = Regexp.last_match(:rhs) 45 | 46 | /(?[\w+\-*]+)\.(?[\w+\-*]+)/ =~ vari 47 | awda = Regexp.last_match(:rhs) 48 | 49 | sqad || awda 50 | end 51 | 52 | def self.match_variable(contents) 53 | /(?\w+)\s*=\s*(?\w*\W*)/ =~ contents 54 | equals_var = Regexp.last_match(:lhs) 55 | 56 | /(?(let|var|const))\s{1,}(?[\w\-]*)/ =~ contents 57 | lazy_init_var = Regexp.last_match(:rhs) 58 | 59 | /(?\w+)\s*(?[\(\w+\)]*)/ =~ contents 60 | func_call_var = Regexp.last_match(:lhs) 61 | 62 | lazy_init_var || func_call_var || equals_var 63 | end 64 | 65 | def self.create_variables_check_info(count_vs_var, lines_vs_var, filename) 66 | err_type = 'UNUSED_VAR_ERR' 67 | 68 | lines_with_unused_var = [] 69 | 70 | count_vs_var.each { |var_a, counter| 71 | lines_vs_var.each { |line, var_b| 72 | lines_with_unused_var << line if counter == 1 && var_a == var_b 73 | raise_err(line, err_type, filename, var_b) if counter == 1 && var_a == var_b 74 | } 75 | } 76 | 77 | lines_with_unused_var 78 | end 79 | 80 | def self.check_unused_var(file) 81 | variable_instances = [] 82 | var_instances_count_hash = {} 83 | lines_variables_hash = {} 84 | 85 | line_check = lambda { |line| 86 | commented_line = line.content.to_s.match?(%r{^\W+[\/\/]}) 87 | 88 | from_parentsis = vars_parentesis(line.content.to_s) if !commented_line 89 | lines_variables_hash[line.number] = from_parentsis if from_parentsis 90 | variable_instances << from_parentsis if !check_escapable(from_parentsis) && !from_parentsis.nil? 91 | 92 | detected_var = match_variable(line.content.to_s) if !commented_line 93 | detected_accessd_var = match_accessd_var(line.content.to_s) if !commented_line 94 | 95 | lines_variables_hash[line.number] = detected_var if detected_var 96 | lines_variables_hash[line.number] = detected_accessd_var if detected_accessd_var 97 | 98 | variable_instances << detected_var if !check_escapable(detected_var) && !detected_var.nil? 99 | variable_instances << detected_accessd_var if !check_escapable(detected_accessd_var) && !detected_accessd_var.nil? 100 | } 101 | 102 | file.lines.each(&line_check) 103 | variable_instances.map { |el| 104 | var_instances_count_hash[el] = var_instances_count_hash[el] ? var_instances_count_hash[el] += 1 : 1 105 | } 106 | 107 | create_variables_check_info(var_instances_count_hash, lines_variables_hash, file.filename) 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/jscop/version.rb: -------------------------------------------------------------------------------- 1 | module Jscop 2 | VERSION = '0.1.9'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/asset.js: -------------------------------------------------------------------------------- 1 | // **values here affect Rspec results** 2 | 3 | var specVar = 9 -------------------------------------------------------------------------------- /spec/class_name_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/jscop/class_name' 2 | 3 | RSpec.describe ClassName do 4 | let(:bad_class_1) { 'class Ema-ker {}' } 5 | let(:bad_class_2) { 'class ambadtoo {}' } 6 | let(:good_class) { 'class GoodName {}' } 7 | 8 | describe '#bad_class_name method' do 9 | it('Should return TRUE if WRONG Class name is supplied') do 10 | actual = ClassName.bad_class_name(bad_class_1) 11 | expect(actual).to eq(true) 12 | end 13 | 14 | it('Should return TRUE if WRONG Class name is supplied') do 15 | actual = ClassName.bad_class_name(bad_class_2) 16 | expect(actual).to eq(true) 17 | end 18 | 19 | it('Should return FALSE if RIGHT Class name is supplied') do 20 | actual = ClassName.bad_class_name(good_class) 21 | expect(actual).to eq(false) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/js_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/classes/js_parser' 2 | 3 | RSpec.describe JsParser do 4 | let(:js_file) { JsParser.new('./spec/asset.js') } 5 | 6 | describe 'def set_lines method' do 7 | it('Should Parse the JS file passed and set the Filename for each line') do 8 | actual = js_file.lines[0].filename 9 | expect(actual).to eq('./spec/asset.js') 10 | end 11 | 12 | it('Should Parse a JS file passed to it into Array of Lines') do 13 | actual = js_file.lines[0].content 14 | expect(actual).to eq('// **values here affect Rspec results**') 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/jscop_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Jscop do 2 | it 'has a version number' do 3 | expect(Jscop::VERSION).not_to be nil 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/line_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/classes/js_parser' 2 | require_relative '../lib/classes/line.rb' 3 | 4 | RSpec.describe JsParser do 5 | let(:js_file) { JsParser.new('./spec/asset.js') } 6 | let(:number) { js_file.lines[1].number } 7 | let(:content) { js_file.lines[1].content } 8 | let(:filename) { js_file.lines[1].filename } 9 | let(:define_line) { Line.new(number, content, filename) } 10 | 11 | describe 'def set_lines method' do 12 | it('Should set line number by Adding 1 to the index of the JSParser Array elements(lines)') do 13 | actual = define_line.number 14 | expect(actual).to eq(2) 15 | end 16 | 17 | it('Should set line contents by Adding contents at corresponding JsParser index + 1 content') do 18 | actual = define_line.content 19 | expect(actual).to eq(content) 20 | end 21 | 22 | it('Should set line source by Adding originating Filename corresponding to JsParser index + 1 name') do 23 | actual = define_line.filename 24 | expect(actual).to eq(filename) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/naming_checker_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/jscop/naming_checker' 2 | 3 | RSpec.describe NamingChecker do 4 | let(:bad_name) { 'var 8meka = 8' } 5 | let(:good_name) { 'var playMaker = 8' } 6 | 7 | describe '#bad_var_case method' do 8 | it('Should return TRUE if WRONG Variable is supplied') do 9 | actual = NamingChecker.bad_var_case(bad_name) 10 | expect(actual).to eq(true) 11 | end 12 | 13 | it('Should return FALSE if RIGHT Variable is supplied') do 14 | actual = NamingChecker.bad_var_case(good_name) 15 | expect(actual).to eq(false) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/spacing_checker_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/jscop/spacing_checker' 2 | 3 | RSpec.describe ClassName do 4 | let(:bad_spacing_1) { 'class Emaker {}' } 5 | let(:bad_spacing_2) { 'var soil = 6' } 6 | let(:bad_spacing_3) { 'var soil = 6' } 7 | let(:bad_spacing_4) { 'var soil = 6' } 8 | let(:bad_spacing_5) { ' var soil = 6' } 9 | let(:good_spacing) { 'class GoodName {}' } 10 | 11 | describe '#found_spaces method' do 12 | it('Should return TRUE if WRONG spacing is detected') do 13 | actual = SpacingChecker.found_spaces(bad_spacing_1) 14 | expect(actual).to eq(true) 15 | end 16 | 17 | it('Should return TRUE if WRONG spacing is detected') do 18 | actual = SpacingChecker.found_spaces(bad_spacing_2) 19 | expect(actual).to eq(true) 20 | end 21 | 22 | it('Should return TRUE if WRONG spacing is detected before the = sign') do 23 | actual = SpacingChecker.found_spaces(bad_spacing_3) 24 | expect(actual).to eq(true) 25 | end 26 | 27 | it('Should return TRUE if WRONG spacing is detected after the = sign') do 28 | actual = SpacingChecker.found_spaces(bad_spacing_4) 29 | expect(actual).to eq(true) 30 | end 31 | 32 | it('Should return FALSE if RIGHT spacing is detected') do 33 | actual = SpacingChecker.found_spaces(good_spacing) 34 | expect(actual).to eq(false) 35 | end 36 | end 37 | 38 | describe '#line_beginining_spaces method' do 39 | it('Should return TRUE if WRONG spacing is detected At begining of Line') do 40 | actual = SpacingChecker.line_beginining_spaces(bad_spacing_5) 41 | expect(actual).to eq(true) 42 | end 43 | 44 | it('Should return FALSE if Right spacing is observed at begining of line') do 45 | actual = SpacingChecker.line_beginining_spaces(good_spacing) 46 | expect(actual).to eq(false) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "jscop" 3 | 4 | RSpec.configure do |config| 5 | # Enable flags like --only-failures and --next-failure 6 | config.example_status_persistence_file_path = ".rspec_status" 7 | 8 | # Disable RSpec exposing methods globally on `Module` and `main` 9 | config.disable_monkey_patching! 10 | 11 | config.expect_with :rspec do |c| 12 | c.syntax = :expect 13 | end 14 | end 15 | --------------------------------------------------------------------------------