Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit b5b7a4f

Browse files
toddmazierskiwfleming
authored andcommitted
Add TypeScript analyzer
Similar in approach to codeclimate/codeclimate-structure#275, this is virtually identical to the `Javascript` one, but with different `PATTERNS`, `LANGUAGE`, and `REQUEST_PATH`. The specs have been modified to include some TypeScript syntax. Addresses codeclimate/app#6325.
1 parent c092735 commit b5b7a4f

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# frozen_string_literal: true
2+
3+
require "cc/engine/analyzers/analyzer_base"
4+
5+
module CC
6+
module Engine
7+
module Analyzers
8+
module TypeScript
9+
class Main < CC::Engine::Analyzers::Base
10+
PATTERNS = [
11+
"**/*.ts",
12+
]
13+
LANGUAGE = "typescript"
14+
DEFAULT_MASS_THRESHOLD = 45
15+
DEFAULT_FILTERS = [
16+
"(ImportDeclaration ___)",
17+
"(VariableDeclarator _ (init (CallExpression (_ (Identifier require)) ___)))",
18+
]
19+
POINTS_PER_OVERAGE = 30_000
20+
REQUEST_PATH = "/typescript"
21+
22+
def use_sexp_lines?
23+
false
24+
end
25+
26+
private
27+
28+
def process_file(file)
29+
parse(file, REQUEST_PATH)
30+
end
31+
32+
def default_filters
33+
DEFAULT_FILTERS.map { |filter| Sexp::Matcher.parse filter }
34+
end
35+
end
36+
end
37+
end
38+
end
39+
end

‎lib/cc/engine/duplication.rb‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require "cc/engine/analyzers/php/main"
99
require "cc/engine/analyzers/python/main"
1010
require "cc/engine/analyzers/reporter"
11+
require "cc/engine/analyzers/typescript/main"
1112
require "cc/engine/analyzers/engine_config"
1213
require "cc/engine/analyzers/sexp"
1314
require "flay"
@@ -22,6 +23,7 @@ class Duplication
2223
"javascript" => ::CC::Engine::Analyzers::Javascript::Main,
2324
"php" => ::CC::Engine::Analyzers::Php::Main,
2425
"python" => ::CC::Engine::Analyzers::Python::Main,
26+
"typescript" => ::CC::Engine::Analyzers::TypeScript::Main,
2527
}.freeze
2628

2729
def initialize(directory:, engine_config:, io:)

‎spec/cc/engine/analyzers/engine_config_spec.rb‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"javascript" => {},
4949
"php" => {},
5050
"python" => {},
51+
"typescript" => {},
5152
})
5253
end
5354

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
require 'spec_helper'
2+
require 'cc/engine/analyzers/typescript/main'
3+
require 'cc/engine/analyzers/reporter'
4+
require 'cc/engine/analyzers/engine_config'
5+
6+
RSpec.describe CC::Engine::Analyzers::TypeScript::Main, in_tmpdir: true do
7+
include AnalyzerSpecHelpers
8+
9+
describe "#run" do
10+
it "prints an issue for identical code" do
11+
create_source_file("foo.ts", <<-EOTS)
12+
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" }
13+
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" }
14+
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" }
15+
EOTS
16+
17+
issues = run_engine(engine_conf).strip.split("0円")
18+
result = issues.first.strip
19+
json = JSON.parse(result)
20+
21+
expect(json["type"]).to eq("issue")
22+
expect(json["check_name"]).to eq("identical-code")
23+
expect(json["description"]).to eq("Identical blocks of code found in 3 locations. Consider refactoring.")
24+
expect(json["categories"]).to eq(["Duplication"])
25+
expect(json["location"]).to eq({
26+
"path" => "foo.ts",
27+
"lines" => { "begin" => 1, "end" => 1 },
28+
})
29+
expect(json["remediation_points"]).to eq(990_000)
30+
expect(json["other_locations"]).to eq([
31+
{"path" => "foo.ts", "lines" => { "begin" => 2, "end" => 2} },
32+
{"path" => "foo.ts", "lines" => { "begin" => 3, "end" => 3} },
33+
])
34+
expect(json["content"]["body"]).to match(/This issue has a mass of 24/)
35+
expect(json["fingerprint"]).to eq("a53b767d2f602f832540ef667ca0618f")
36+
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR)
37+
end
38+
39+
it "prints an issue for similar code" do
40+
create_source_file("foo.ts", <<-EOTS)
41+
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" }
42+
enum Direction { Up = "up", Down = "down", Left = "left", Right = "right" }
43+
enum Direction { up = "UP", down = "DOWN", left = "LEFT", right = "RIGHT" }
44+
EOTS
45+
46+
issues = run_engine(engine_conf).strip.split("0円")
47+
result = issues.first.strip
48+
json = JSON.parse(result)
49+
50+
expect(json["type"]).to eq("issue")
51+
expect(json["check_name"]).to eq("similar-code")
52+
expect(json["description"]).to eq("Similar blocks of code found in 3 locations. Consider refactoring.")
53+
expect(json["categories"]).to eq(["Duplication"])
54+
expect(json["location"]).to eq({
55+
"path" => "foo.ts",
56+
"lines" => { "begin" => 1, "end" => 1 },
57+
})
58+
expect(json["remediation_points"]).to eq(990_000)
59+
expect(json["other_locations"]).to eq([
60+
{"path" => "foo.ts", "lines" => { "begin" => 2, "end" => 2} },
61+
{"path" => "foo.ts", "lines" => { "begin" => 3, "end" => 3} },
62+
])
63+
expect(json["content"]["body"]).to match(/This issue has a mass of 24/)
64+
expect(json["fingerprint"]).to eq("ede3452b637e0bc021541e6369b9362e")
65+
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR)
66+
end
67+
68+
it "handles ES6 spread params" do
69+
create_source_file("foo.tsx", <<-EOTS)
70+
const ThingClass = React.createClass({
71+
propTypes: {
72+
...OtherThing.propTypes,
73+
otherProp: "someVal"
74+
}
75+
});
76+
EOTS
77+
78+
expect(CC.logger).not_to receive(:info).with(/Skipping file/)
79+
run_engine(engine_conf)
80+
end
81+
82+
it "skips unparsable files" do
83+
create_source_file("foo.ts", <<-EOTS)
84+
function () { do(); // missing closing brace
85+
EOTS
86+
87+
expect(CC.logger).to receive(:warn).with(/Skipping \.\/foo\.ts/)
88+
expect(CC.logger).to receive(:warn).with("Response status: 422")
89+
expect(run_engine(engine_conf)).to eq("")
90+
end
91+
92+
it "handles parser 500s" do
93+
create_source_file("foo.ts", <<-EOTS)
94+
EOTS
95+
96+
error = CC::Parser::Client::HTTPError.new(500, "Error processing file: ./foo.ts")
97+
allow(CC::Parser).to receive(:parse).with("", "/typescript").and_raise(error)
98+
99+
expect(CC.logger).to receive(:error).with("Error processing file: ./foo.ts")
100+
expect(CC.logger).to receive(:error).with(error.message)
101+
102+
expect { run_engine(engine_conf) }.to raise_error(error)
103+
end
104+
end
105+
106+
it "does not flag duplicate comments" do
107+
create_source_file("foo.ts", <<-EOTS)
108+
// A comment.
109+
// A comment.
110+
111+
/* A comment. */
112+
/* A comment. */
113+
EOTS
114+
115+
expect(run_engine(engine_conf)).to be_empty
116+
end
117+
118+
it "ignores imports" do
119+
create_source_file("foo.ts", <<~EOTS)
120+
import React, { Component, PropTypes } from 'react'
121+
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow } from 'material-ui/Table'
122+
import values from 'lodash/values'
123+
import { v4 } from 'uuid'
124+
EOTS
125+
126+
create_source_file("bar.ts", <<~EOTS)
127+
import React, { Component, PropTypes } from 'react'
128+
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow } from 'material-ui/Table'
129+
import values from 'lodash/values'
130+
import { v4 } from 'uuid'
131+
EOTS
132+
133+
issues = run_engine(engine_conf).strip.split("0円")
134+
expect(issues).to be_empty
135+
end
136+
137+
it "ignores requires" do
138+
create_source_file("foo.ts", <<~EOTS)
139+
const a = require('foo'),
140+
b = require('bar'),
141+
c = require('baz'),
142+
d = require('bam');
143+
EOTS
144+
145+
create_source_file("bar.ts", <<~EOTS)
146+
const a = require('foo'),
147+
b = require('bar'),
148+
c = require('baz'),
149+
d = require('bam');
150+
EOTS
151+
152+
issues = run_engine(engine_conf).strip.split("0円")
153+
expect(issues).to be_empty
154+
end
155+
156+
it "outputs the correct line numbers for ASTs missing line details (codeclimate/app#6227)" do
157+
create_source_file("foo.ts", <<~EOTS)
158+
`/movie?${getQueryString({ movie_id: movieId })}`
159+
EOTS
160+
161+
create_source_file("bar.ts", <<~EOTS)
162+
var greeting = "hello";
163+
164+
`/movie?${getQueryString({ movie_id: movieId })}`
165+
EOTS
166+
167+
issues = run_engine(engine_conf).strip.split("0円")
168+
expect(issues).to_not be_empty
169+
170+
issues.map! { |issue| JSON.parse(issue) }
171+
172+
foo_issue = issues.detect { |issue| issue.fetch("location").fetch("path") == "foo.ts" }
173+
expect(foo_issue["location"]).to eq({
174+
"path" => "foo.ts",
175+
"lines" => { "begin" => 1, "end" => 1 },
176+
})
177+
178+
bar_issue = issues.detect { |issue| issue.fetch("location").fetch("path") == "bar.ts" }
179+
expect(bar_issue["location"]).to eq({
180+
"path" => "bar.ts",
181+
"lines" => { "begin" => 3, "end" => 3 },
182+
})
183+
end
184+
185+
def engine_conf
186+
CC::Engine::Analyzers::EngineConfig.new({
187+
'config' => {
188+
'checks' => {
189+
'similar-code' => {
190+
'enabled' => true,
191+
},
192+
'identical-code' => {
193+
'enabled' => true,
194+
},
195+
},
196+
'languages' => {
197+
'typescript' => {
198+
'mass_threshold' => 1,
199+
},
200+
},
201+
},
202+
})
203+
end
204+
end

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /