Class: Guardrails::Tokens
- Inherits:
-
Object
- Object
- Guardrails::Tokens
show all
- Defined in:
- lib/guardrails/tokens.rb,
lib/guardrails/tokens/tailwind_config_parser.rb
Defined Under Namespace
Classes: Drift, TailwindConfigParser, Token
Constant Summary
collapse
- CSS_VAR_PATTERN =
/--([a-z][\w-]*):\s*([^;]+);/i
- SCSS_VAR_PATTERN =
/\$([a-z][\w-]*):\s*([^;]+);/i
- HEX_LITERAL_PATTERN =
/#[0-9a-fA-F]{3,8}\b/
/\/\*[\s\S]*?\*\//
/\/\/[^\n]*/
- STYLESHEET_PATTERNS =
[
"app/assets/stylesheets/**/*.{css,scss}",
"app/assets/tailwind/**/*.css"
].freeze
- IMPLICIT_IGNORE_SEGMENTS =
Same path-component skip-list as Audit / StackDetector — vendor stylesheets nested under app/assets/stylesheets/ shouldn’t surface as drift since they’re typically third-party.
%w[vendor node_modules tmp public log].freeze
Instance Method Summary
collapse
Constructor Details
#initialize(root:, output: $stdout) ⇒ Tokens
Returns a new instance of Tokens.
28
29
30
31
32
|
# File 'lib/guardrails/tokens.rb', line 28
def initialize(root:, output: $stdout)
@root = Pathname(root)
@output = output
@config = load_config
end
|
Instance Method Details
#detect_drift(tokens) ⇒ Object
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
# File 'lib/guardrails/tokens.rb', line 91
def detect_drift(tokens)
lookup = tokens.to_h { |t| [HexNormalizer.normalize(t.value), t] }
drift = []
definition_files = [colors_file, type_scale_file].compact
stylesheets.each do |file|
next if definition_files.include?(file)
next if file == @root.join("tailwind.config.js")
raw_content = File.read(file, encoding: Encoding::UTF_8)
content = (raw_content)
content.each_line.with_index do |line, idx|
next if variable_definition_line?(line)
line.scan(HEX_LITERAL_PATTERN) do
value = Regexp.last_match[0]
column = Regexp.last_match.begin(0) + 1
drift << Drift.new(
file: file.relative_path_from(@root).to_s,
line: idx + 1,
column: column,
value: value,
matched_token: lookup[HexNormalizer.normalize(value)]
)
end
end
end
drift
end
|
#parse_tailwind_config ⇒ Object
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
# File 'lib/guardrails/tokens.rb', line 55
def parse_tailwind_config
@tailwind_preset_hint = nil
file = @root.join("tailwind.config.js")
return [] unless file.exist?
content = File.read(file, encoding: Encoding::UTF_8)
entries = TailwindConfigParser.parse(content)
if entries.empty? && tailwind_uses_presets?(content)
@tailwind_preset_hint =
"tailwind.config.js uses a `presets:` import; only the literal config file " \
"is parsed (we don't evaluate JS). Define non-color tokens in v4 `@theme` " \
"blocks for cross-tool token visibility."
end
entries.map do |entry|
Token.new(
name: entry.name,
value: entry.value,
syntax: :tailwind,
file: file.relative_path_from(@root).to_s,
line: 0
)
end
end
|
#parse_tokens ⇒ Object
42
43
44
45
46
47
48
49
50
51
52
53
|
# File 'lib/guardrails/tokens.rb', line 42
def parse_tokens
tokens = []
[colors_file, type_scale_file].compact.each do |file|
next unless file.exist?
content = File.read(file, encoding: Encoding::UTF_8)
tokens.concat(scan(content, file, CSS_VAR_PATTERN, :css_var))
tokens.concat(scan(content, file, SCSS_VAR_PATTERN, :scss_var))
end
tokens.concat(parse_tailwind_config)
tokens
end
|
#run ⇒ Object
34
35
36
37
38
39
40
|
# File 'lib/guardrails/tokens.rb', line 34
def run
tokens = parse_tokens
drift = detect_drift(tokens)
print_summary(tokens)
print_drift(drift)
{ tokens: tokens, drift: drift }
end
|