Skip to content
Browse files

0.1

  • Loading branch information...
1 parent 4d3cbe4 commit f9c2acfdd09d6997ba9545f98b128279afdd2567 @robbywalker robbywalker committed
View
202 LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
View
1 MANIFEST.in
@@ -0,0 +1 @@
+include src/ocstyle/testdata/*
View
55 README.md
@@ -2,3 +2,58 @@ ocstyle
=======
Objective-C style checker
+
+# Installation
+
+pip install ocstyle
+
+# Example
+
+```objc
++(void) someMessage:(NSString*)subdomain {
+ NSString *ShouldStartLowerCase;
+ // ...
+}
+```
+
+```$ oclint test.m
+test.m
+ERROR: 1:1 [1] - MissingSpace - Expected 1, got 0
+ERROR: 1:8 [8] - ExtraSpace - Did not expect ' ' here
+ERROR: 1:29 [29] - MissingSpace - Expected 1, got 0
+ERROR: 1:41 [41] - MissingNewline - Should have newline after ;
+ERROR: 1:41 [41] - MissingSemicolon - Expected a semicolon
+ERROR: 2:35 [77] - BadLocalVariableName - Local variable must start with a lower case letter
+```
+
+# Goal
+
+Make it easy to share and enforce style rules for Objective C. The less human time we spend thinking about whitespace
+and naming the better! Also enforces the existence of basic docs.
+
+# Related
+
+[OCLint](http://oclint.org/) runs static analysis that helps to detect common logic errors. Use it alongside ocstyle!
+
+# Status
+
+This is a pretty early stage project. We fully expect bugs and feature requests. One notable absence is that right
+now style rules are not configurable. For example, we use the following style for messages in `.m` files:
+
+```objc
++(void) someMessage:(NSString*)subdomain;
+{
+}
+```
+
+Note the inclusion of the `;` and the `{` being on the next line. We like this style because it makes it easy to copy
+and paste from `.h` to `.m` and back, but maybe you have your own preferences. We'd be very happy to accept pull
+requests that make ocstyle more configurable.
+
+# License
+
+Apache License version 2.0
+
+# Authors
+
+[Cue](http://www.cueup.com)
View
41 setup.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# Copyright 2013 The ocstyle Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Setup script for ocstyle."""
+
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+
+setup(name='ocstyle',
+ version='0.1',
+ description='Objective-C style checker',
+ author='Cue Technologies, Inc.',
+ url='https://www.github.com/Cue/ocstyle',
+ package_dir={'': 'src'},
+ packages=['ocstyle'],
+ test_suite='nose.collector',
+ include_package_data=True,
+ install_requires=[
+ 'parcon==0.1.25'
+ ],
+ entry_points = {
+ 'console_scripts': [
+ 'ocstyle = ocstyle.main:main'
+ ]
+ },
+)
View
15 src/ocstyle/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2013 The ocstyle Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Package for ocstyle"""
View
46 src/ocstyle/error.py
@@ -0,0 +1,46 @@
+# Copyright 2012 The ocstyle Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Objective C style error type."""
+
+import bisect
+
+
+
+class Error(object):
+ """An error."""
+
+ __slots__ = ('kind', 'position', 'message', 'lines')
+
+
+ def __init__(self, kind, message, position, lines):
+ self.kind = kind
+ self.position = position
+ self.message = message
+ self.lines = lines
+
+
+ def lineAndOffset(self):
+ """Return the line and offset where this error occurred."""
+ line = bisect.bisect_left(self.lines, self.position)
+ return line, self.position - self.lines[line - 1]
+
+
+ def __str__(self):
+ line, offset = self.lineAndOffset()
+ return '%d:%d [%d] - %s - %s' % (line, offset, self.position, self.kind, self.message)
+
+
+ def __repr__(self):
+ return 'Error<%s>' % self
View
70 src/ocstyle/handlers.py
@@ -0,0 +1,70 @@
+# Copyright 2012 The ocstyle Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Objective C parse handlers."""
+
+from parcon import flatten
+
+import StringIO
+
+from ocstyle.error import Error
+
+
+def drop(*_):
+ """Drops output."""
+ return None
+
+
+def justErrors(value):
+ """Check value and ensure it contains no text, only errors (or nothing)."""
+ if not value:
+ return None
+
+ if not isinstance(value, list):
+ value = [value]
+
+ result = []
+ for part in flatten(value):
+ if part:
+ if not isinstance(part, Error):
+ raise Exception('Got %r when expecting only errors' % part)
+ else:
+ result.append(part)
+
+ return value
+
+
+def stringsAndErrors(value):
+ """Aggregate both unparsed strings and errors."""
+ if not value:
+ return None
+
+ if not isinstance(value, list):
+ value = [value]
+
+ result = []
+ lastStringPart = None
+ for part in flatten(value):
+ if isinstance(part, basestring):
+ lastStringPart = lastStringPart or StringIO.StringIO()
+ lastStringPart.write(part)
+ else:
+ if lastStringPart:
+ result.append(lastStringPart.getvalue())
+ lastStringPart = None
+ if part:
+ result.append(part)
+ if lastStringPart:
+ result.append(lastStringPart.getvalue())
+ return result
View
58 src/ocstyle/main.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# Copyright 2012 The ocstyle Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Basic Objective C style checker."""
+
+import os.path
+import sys
+
+import parcon
+
+from ocstyle import rules
+
+
+def check(path):
+ """Style checks the given path."""
+ with open(path) as f:
+ return checkFile(path, f)
+
+
+def checkFile(path, f):
+ """Style checks the given file object."""
+ content = f.read()
+ lineErrors = rules.setupLines(content)
+ result = parcon.Exact(rules.entireFile).parse_string(content)
+ if path.endswith(('.m', '.mm')):
+ result = [err for err in result if not isinstance(err, rules.Error) or not err.kind.endswith('InHeader')]
+ result.extend(lineErrors)
+ result.sort(key=lambda err: err.position if isinstance(err, rules.Error) else 0)
+ return result
+
+
+def main():
+ """Main body of the script."""
+ for filename in sys.argv[1:]:
+ if not os.path.isdir(filename):
+ print filename
+ for part in check(filename):
+ if isinstance(part, rules.Error):
+ print 'ERROR: %s' % part
+ else:
+ print 'unparsed: %r' % part
+ print
+
+
+if __name__ == '__main__':
+ main()
View
103 src/ocstyle/main_test.py
@@ -0,0 +1,103 @@
+# Copyright 2012 The ocstyle Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for the Objective C style checker."""
+
+import os.path
+import pkg_resources
+import unittest
+
+from ocstyle import main
+
+
+
+class StyleCheckerTest(unittest.TestCase):
+ """"Tests for the Objective C style checker."""
+
+ def assertSameErrors(self, expected, actual):
+ """Assert that we reported the errors that we were supposed to."""
+ actual.sort()
+ expected.sort()
+
+ unexpected = []
+ missing = []
+
+ i = 0
+ j = 0
+ while i < len(actual) and j < len(expected):
+ if actual[i] == expected[j]:
+ i += 1
+ j += 1
+ elif actual[i] > expected[j]:
+ missing.append(expected[j])
+ j += 1
+ else:
+ unexpected.append(actual[i])
+ i += 1
+
+ unexpected.extend(actual[i:])
+ missing.extend(expected[j:])
+
+ messages = []
+ for item in unexpected:
+ messages.append('Got unexpected error "%s" at line %d' % item)
+
+ for item in missing:
+ messages.append('Expected error but was not reported: "%s" at line %d' % item)
+
+ if messages:
+ self.fail('\n'.join(messages))
+
+
+ def testSampleFiles(self):
+ """Iterates through sample files and verifies if the expected style errors are reported."""
+ files = [filename for filename in pkg_resources.resource_listdir('ocstyle', 'testdata')
+ if filename.endswith(('.h', '.m', 'mm'))]
+ self.assertNotEquals([], files)
+ for filename in files:
+ print filename # OK to print in a test. # pylint: disable=W9914
+
+ errors = []
+ badParse = []
+ with pkg_resources.resource_stream('ocstyle', os.path.join('testdata', filename)) as f:
+ result = main.checkFile(filename, f)
+ for part in result:
+ if isinstance(part, basestring):
+ badParse.append(part)
+ else:
+ errors.append((part.kind, part.lineAndOffset()[0]))
+
+ if badParse:
+ self.fail('Failed to parse : %r' % badParse)
+
+ expected = []
+ with pkg_resources.resource_stream('ocstyle', os.path.join('testdata', filename)) as f:
+ lineNumber = 1
+ for line in f:
+ _, _, expect = line.partition('// EXPECT')
+ if expect:
+ offset, _, errorList = expect.partition(':')
+ if offset:
+ if offset[0] == '+':
+ offset = int(offset[1:])
+ else:
+ offset = - int(offset[1:])
+ else:
+ offset = 0
+ for error in errorList.split(','):
+ expected.append((error.strip(), lineNumber + offset))
+ error.strip()
+ lineNumber += 1
+
+ self.assertSameErrors(expected, errors)
View
611 src/ocstyle/rules.py
@@ -0,0 +1,611 @@
+# Copyright 2012 The ocstyle Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Objective C style rules."""
+
+import functools
+from parcon import AnyChar, First, Forward, Literal, Present, Regex, Translate, SignificantLiteral
+from parcon import separated
+from parcon import failure, match
+
+import inspect
+import re
+
+from ocstyle.error import Error
+from ocstyle.handlers import drop, justErrors, stringsAndErrors
+
+
+VERBOSE = True
+
+TAB_SIZE = 4
+
+MAX_LINE_LENGTH = 120
+
+LINES = []
+
+# PyLint has a very hard time with our decorator pattern. # pylint: disable=E1120
+
+
+def setupLines(content):
+ """Setup line position data."""
+ LINES[:] = []
+ pos = -1
+ LINES.append(0)
+ while True:
+ pos = content.find('\n', pos + 1)
+ if pos == -1:
+ break
+ LINES.append(pos)
+
+ errors = []
+ for lineNo in range(1, len(LINES)):
+ lineLength = LINES[lineNo] - LINES[lineNo - 1] - 1 # Remove the \n character.
+ if lineLength > MAX_LINE_LENGTH:
+ errors.append(Error(
+ 'LineTooLong', 'Line too long: %d chars over the %d limit' % (lineLength, MAX_LINE_LENGTH),
+ LINES[lineNo], LINES))
+ return errors
+
+
+
+class TranslateWithPosition(Translate):
+ """Like Translate, but also passes position."""
+
+ def __init__(self, parser, function, passPosition=None):
+ Translate.__init__(self, parser, function)
+ self._passPosition = len(inspect.getargspec(function).args) == 2 if passPosition is None else passPosition
+
+
+ def parse(self, text, position, endPosition, space):
+ result = self.parser.parse(text, position, endPosition, space)
+ if not result:
+ return failure(result.expected)
+ if self._passPosition:
+ translated = self.function(result.value, result.end)
+ else:
+ translated = self.function(result.value)
+ return match(result.end, translated, result.expected)
+
+
+def rule(parserPart):
+ """Decorator for rule syntax."""
+
+ def decorator(f):
+ """The actual decorator."""
+ return TranslateWithPosition(parserPart, f)
+
+ return decorator
+
+
+def noOut(_):
+ """Outputs nothing."""
+ return None
+
+
+
+def keep(literal):
+ """Shorter name for this function."""
+ return SignificantLiteral(literal)
+
+
+def unexpectedHandler(kind, value, pos):
+ """Handle a syntactically but not stylistically valid token."""
+ return Error(kind, 'Did not expect %r here' % value, pos, LINES)
+
+
+def unexpected(kind, pattern):
+ """Accept a pattern but mark it as a style error."""
+ return -TranslateWithPosition(pattern, functools.partial(unexpectedHandler, kind), True)
+
+
+def expectedHandler(kind, message, value, pos):
+ """Handle a syntactically but not stylistically optional token."""
+ if not value:
+ return Error(kind, message, pos, LINES)
+
+
+def expected(kind, message, pattern):
+ """Accept a pattern or nothing but mark missing it as a style error."""
+ return TranslateWithPosition(-(pattern[lambda _: True]),
+ functools.partial(expectedHandler, kind, message), True)
+
+
+def sp(expectedCount):
+ """A space that matches any number of spaces but stylistically expects just one."""
+
+ def cb(value, pos):
+ """The callback for the rule."""
+ count = len(value)
+ if expectedCount > count:
+ return Error('MissingSpace', 'Expected %d, got %d' % (expectedCount, count), pos, LINES)
+ elif expectedCount < count:
+ return Error('ExtraSpace', 'Expected %d, got %d' % (expectedCount, count), pos, LINES)
+
+ return TranslateWithPosition(Regex(r'[ \t]*'), cb)
+
+
+xsp = unexpected('ExtraSpace', Regex(r'[ \t]+')) # Breaking naming scheme to match functions. # pylint: disable=C0103
+
+nlOrSp = '\n' | sp(1) # Breaking naming scheme to match functions. # pylint: disable=C0103
+
+
+@rule(Regex(r'[ \t]*//[^\n]*')) # Consume spaces since any number of leading spaces before a comment is ok.
+def lineComment(value, pos):
+ """A line comment."""
+ value = value.lstrip()
+ if len(value) > 2 and value[2] != ' ':
+ return Error('MissingSpace', 'Should have space after //', pos, LINES)
+
+
+@rule(Regex(r'#(pragma|ifdef|endif|else|if|define)(([^\\\n]+)|(\\[ \t]*\S)|(\\[ \t]*\n))*'))
+def directive(_):
+ """A preprocessor command, possibly multi-line."""
+ return None
+
+
+@rule(Regex(r'[ \t]*/\*([^*]|\*[^/])+\*/')) # Consume spaces since any number of leading spaces before a comment is ok.
+def docComment(value, pos):
+ """A doc comment."""
+ value = value.lstrip()
+ stripped = value.lstrip('/').lstrip('*')
+ if stripped and stripped[0] not in (' ', '\n'):
+ return Error('MissingSpace', 'Should have space after /*', pos, LINES)
+
+
+@rule(lineComment | docComment | directive)
+def anyPreprocessor(value):
+ """Any text that is processed by the preprocessor."""
+ return justErrors(value)
+
+
+@rule(Regex(r'\d+(\.\d+)?'))
+def number(_):
+ """A number."""
+ return None
+
+
+@rule(Regex(r'"([^\"]|\\.)*"'))
+def string(_):
+ """A C string."""
+ return None
+
+
+@rule('@' + string)
+def objcString(value):
+ """An objective C string."""
+ return justErrors(value)
+
+
+@rule(Regex(r'<[^>]+>'))
+def systemInclude(_):
+ """System inclusion path."""
+ return None
+
+
+@rule('#' + First('import', 'include') + sp(1) + (string | systemInclude))
+def inclusion(value):
+ """Inclusion of another file."""
+ return justErrors(value)
+
+
+@rule(Regex(r'[a-zA-Z_][a-zA-Z0-9_]*'))
+def identifier(value):
+ """An identifier."""
+ return value
+
+
+@rule(identifier[1])
+def className(value, position):
+ """A name of a class."""
+ if not value[0].isupper():
+ return Error('BadClassName', 'Class names must be capitalized', position, LINES)
+ return None
+
+
+@rule(identifier[1])
+def selectorPartName(value, position):
+ """A name of a class."""
+ if value[0] == '_' and value[1].islower():
+ return Error('PrivateSelectorInHeader', 'Selectors starting with _ can not be in header files', position, LINES)
+ if not value[0].islower():
+ return Error('BadSelectorPartName', 'Selector names must not be capitalized', position, LINES)
+ return None
+
+
+@rule(identifier[1])
+def ivarName(value, position):
+ """A name of a class."""
+ if not value[0] == '_' or not value[1].islower():
+ return Error(
+ 'BadInstanceVariableName', 'Instance variable names start with _ and not be capitalized', position, LINES)
+ return None
+
+
+@rule(identifier[1])
+def parameterName(value, position):
+ """A name of a class."""
+ if not value[0].islower():
+ return Error('BadParameterName', 'Parameter names must not be capitalized', position, LINES)
+ return None
+
+
+@rule(identifier[1])
+def anyIdentifier(_):
+ """Matches any identifier."""
+ return None
+
+
+@rule(sp(1) + ':' + sp(1) + className + (',' + sp(1) + className)[...])
+def baseClasses(value):
+ """List of base classes."""
+ return justErrors(value)
+
+
+filePart = Forward() # Breaking naming scheme to match functions. # pylint: disable=C0103
+
+
+@rule(Literal('@end'))
+def end(_):
+ """End of an interface, protocol, or implementation."""
+ return None
+
+
+@rule(First('long', 'short') + ' ' + xsp)
+def cTypeSizeModifier(value):
+ """A type size modifier."""
+ return justErrors(value)
+
+
+@rule(Regex(r'((signed|unsigned)\s+)?((long|short)\s+){0,2}(long|int|double|float|short)\b'))
+def sizedCType(value, position):
+ """A type modifier."""
+ for m in re.compile(r'\s\s+').finditer(value):
+ return Error('ExtraSpace', 'Extra space in type name', position + m.start(), LINES)
+ return None
+
+
+@rule(First('volatile', 'static', 'const') + ' ' + xsp)
+def modifier(value):
+ """A type modifier."""
+ return justErrors(value)
+
+
+@rule(xsp + '<' + xsp + anyIdentifier + (',' + ('\n' + xsp | sp(1)) + anyIdentifier)[...] + xsp + '>')
+def implementedProtocols(value):
+ """List of implemented protocols."""
+ return justErrors(value)
+
+
+objcType = Forward() # Breaking naming scheme to match functions. # pylint: disable=C0103
+
+
+@rule('<' + (objcType | number) + (',' + sp(1) + (objcType | number))[...] + '>' + (Present(Literal(':')) | sp(1)))
+def cppTemplateValues(value):
+ """Values for a C++ template."""
+ return justErrors(value)
+
+
+@rule(modifier[...] + (sizedCType | anyIdentifier) + -implementedProtocols +
+ (Present(Regex(r'[(),<>:]')) | sp(1)) + Literal('*')[...])
+def simpleType(value): # 2 lines check is broken due to decorator wrapping. # pylint: disable=W9911
+ """A type."""
+ return justErrors(value)
+
+
+@rule(simpleType + xsp + -parameterName)
+def singleBlockParam(value):
+ """Single parameter for a block."""
+ return justErrors(value)
+
+
+@rule('(' + -(singleBlockParam + xsp + (',' + sp(1) + singleBlockParam)[...]) + ')')
+def blockParams(value):
+ """Parameters to a block declaration."""
+ return justErrors(value)
+
+
+@rule('(^)' + xsp + blockParams)
+def blockSuffix(value):
+ """A suffix that, when appended to a type, makes it a block type."""
+ return justErrors(value)
+
+
+objcType.set(Translate(separated(simpleType + -cppTemplateValues, '::') + -blockSuffix, justErrors))
+
+
+@rule(Regex(r'\[[^]]*]'))
+def arrayCardinality(_):
+ """Matches an array cardinality specification like [4]"""
+ return None
+
+
+def namedVariable(nameType):
+ """Creates a pattern for a named simple or block variable."""
+ return (
+ (objcType + xsp + nameType + -arrayCardinality) |
+ (objcType + '(^' + xsp + nameType + xsp + ')' + xsp + blockParams))
+
+
+@rule(sp(4) + namedVariable(ivarName) + xsp + ';')
+def ivar(value):
+ """An instance variable."""
+ return justErrors(value)
+
+
+@rule(First('@private', '@protected', '@public', '@package'))
+def ivarSection(value):
+ """Section within an ivar block in an interface."""
+ return justErrors(value)
+
+
+@rule('{' + +(ivar | ivarSection | anyPreprocessor | (xsp + '\n')) + '}')
+def ivarBlock(value):
+ """Block full of ivar declarations."""
+ return justErrors(value)
+
+
+@rule(selectorPartName + xsp + ':' + xsp + '(' + xsp + objcType + xsp + ')' + xsp + parameterName)
+def selectorPart(value):
+ """A part of a selector."""
+ return justErrors(value)
+
+
+@rule((xsp + '\n' + +First(' ', '\t')) | sp(1))
+def singleSpaceOrLineWrap(value):
+ """Single space or line wrap."""
+ return justErrors(value)
+
+
+@rule(selectorPart + (singleSpaceOrLineWrap + selectorPart)[...])
+def selectorWithParams(value):
+ """Multipart selector."""
+ return justErrors(value)
+
+
+@rule('(' + xsp + objcType + -docComment + xsp + ')') # TODO(robbyw): More generic fix for random comments.
+def methodReturnType(value):
+ """A method signature return type."""
+ return justErrors(value)
+
+
+@rule(Regex('[-+]')[noOut] + sp(1) + methodReturnType + xsp + (selectorWithParams | selectorPartName))
+def methodSignature(value):
+ """A method signature."""
+ return justErrors(value)
+
+
+@rule(methodSignature + ';')
+def methodDeclaration(value):
+ """A method declaration."""
+ return justErrors(value)
+
+
+@rule(identifier[1])
+def propertyName(value, position):
+ """Checks a property name."""
+ if not value[0].islower():
+ return Error('BadPropertyName', 'Property names must not be capitalized', position, LINES)
+ return None
+
+
+@rule(Regex(r'readonly|atomic|nonatomic|copy|assign|retain|strong|weak')[drop] |
+ (Regex(r'[gs]etter')[drop] + sp(1) + '=' + sp(1) + selectorPartName))
+def propertyOption(value): # 2 lines check is broken due to decorator wrapping. # pylint: disable=W9911
+ """Option for a property."""
+ return justErrors(value)
+
+
+@rule('(' + xsp + propertyOption + xsp + (',' + sp(1) + propertyOption)[...] + xsp + ')' + sp(1))
+def propertyOptions(value):
+ """List of options for a property."""
+ return justErrors(value)
+
+
+def expectedDoc(kind, message):
+ """Expected documentation."""
+ return expected(kind, message, docComment + xsp + '\n' + xsp)
+
+
+@rule(expectedDoc('ExpectedPropertyDocInHeader', 'Property requires /** documentation */') +
+ '@property' + sp(1) + -propertyOptions + -('IBOutlet ' + xsp) + namedVariable(propertyName) + xsp + ';')
+def propertyDeclaration(value): # 2 lines check is broken due to decorator wrapping. # pylint: disable=W9911
+ """A property declaration."""
+ return justErrors(value)
+
+
+@rule(First('@required', '@optional'))
+def declarationSection(value):
+ """Declarations sub-section in an interface or protocol."""
+ return justErrors(value)
+
+
+@rule(anyIdentifier + Regex(r'([^;]*);')[drop])
+def macroCall(value):
+ """A call to a macro."""
+ return justErrors(value)
+
+
+@rule(((xsp + (declarationSection | methodDeclaration | propertyDeclaration)) |
+ macroCall | anyPreprocessor | (xsp + '\n'))[...])
+def declarations(value): # 2 lines check is broken due to decorator wrapping. # pylint: disable=W9911
+ """Declarations area of an interface."""
+ return justErrors(value)
+
+
+@rule(expectedDoc('ExpectedInterfaceDocInHeader', 'Interface requires /** documentation */') +
+ '@interface' + sp(1) + className + -baseClasses +
+ -(nlOrSp + implementedProtocols) +
+ -(nlOrSp + ivarBlock) +
+ declarations +
+ end)
+def interface(value): # 2 lines check is broken due to decorator wrapping. # pylint: disable=W9911
+ """Interface declaration."""
+ return stringsAndErrors(value)
+
+
+@rule(expectedDoc('ExpectedProtocolDocInHeader', 'Protocol requires /** documentation */') +
+ '@protocol' + sp(1) + className + -baseClasses + -(sp(1) + implementedProtocols) + xsp + '\n' +
+ declarations +
+ end)
+def protocolDeclaration(value): # 2 lines check is broken due to decorator wrapping. # pylint: disable=W9911
+ """Interface declaration."""
+ return stringsAndErrors(value)
+
+
+@rule('@implementation' + sp(1) + className + -(sp(1) + ivarBlock) + (filePart - end)[...] + end)
+def implementation(value):
+ """Implementation section."""
+ return stringsAndErrors(value)
+
+
+codeBlock = Forward() # Breaking naming scheme to match functions. # pylint: disable=C0103
+
+
+@rule(identifier[1])
+def namespaceName(value, position):
+ """A name of a namespace."""
+ if not value[0].islower():
+ return Error('BadNamespaceName', 'Namespace name must start with a lower case letter', position, LINES)
+ return None
+
+
+@rule('namespace' + sp(1) + namespaceName + Regex(r'\n?\s*')[drop] + '{' + (filePart - '}')[...] + '}')
+def namespace(value):
+ """Namespace block."""
+ return stringsAndErrors(value)
+
+
+@rule(Regex('(class|struct) ')[drop] + xsp + className + -(Regex('[^{;]+')[drop] + codeBlock[drop]) + ';')
+def cppClass(value):
+ """A C++ class."""
+ # TODO(robbyw): Better parsing here, this is very minimal.
+ return justErrors(value)
+
+
+@rule(identifier[1])
+def localVarName(value, position):
+ """A name of a class."""
+ if not value[0].islower():
+ return Error('BadLocalVariableName', 'Local variable must start with a lower case letter', position, LINES)
+ return None
+
+
+# A near-total hack to stand in for expressions.
+expression = Forward() # Breaking naming scheme to match functions. # pylint: disable=C0103
+
+
+@rule(Regex(r'[^();]*[^();\s]')[drop] | ('(' + -expression + ')'))
+def expressionPart(value):
+ """Just the errors."""
+ return justErrors(value)
+
+
+expression.set(Translate(+expressionPart, stringsAndErrors))
+
+
+@rule(objcType + xsp + localVarName + -(sp(1) + '=' + nlOrSp + expression) + ';')
+def localVar(value):
+ """Just the errors."""
+ return justErrors(value)
+
+
+@rule(Regex('return|if|for|while|switch|case|do|else|new|delete|try|catch|finally|template|continue|break|goto'))
+def keyword(_):
+ """Matches keywords."""
+ return None
+
+
+@rule(+(Regex('[^;{}"/#]+')[drop] | string | anyPreprocessor | '/'))
+def unparsedStmt(value):
+ """Unparsed statement."""
+ return justErrors(value)
+
+
+statement = Forward() # Breaking naming scheme to match functions. # pylint: disable=C0103
+
+
+@rule('if' + sp(1) + '(' + xsp + expression + xsp + ')' + ((sp(1) + codeBlock) | statement))
+def ifStmt(value):
+ """An if statement."""
+ return justErrors(value)
+
+
+@rule('for' + sp(1) + '(' + xsp + expression + xsp + ')' + ((sp(1) + codeBlock) | statement))
+def forStmt(value):
+ """An if statement."""
+ return justErrors(value)
+
+
+@rule('while' + sp(1) + '(' + xsp + expression + xsp + ')' + ((sp(1) + codeBlock) | statement))
+def whileStmt(value):
+ """An if statement."""
+ return justErrors(value)
+
+
+statement.set(Translate(
+ (Regex('\s+')[drop] | ifStmt | forStmt | whileStmt | (keyword + unparsedStmt) | localVar | unparsedStmt)
+ + Literal(';')[...],
+ justErrors))
+
+
+codeBlockBody = +( # Breaking naming scheme to match functions. # pylint: disable=C0103
+ anyPreprocessor | statement | codeBlock)
+
+codeBlock.set(Translate('{' + +codeBlockBody + '}', stringsAndErrors))
+
+
+@rule(Regex('[ \t]*') + -keep('\n'))
+def shouldBeNewline(result, pos):
+ """Expect a newline here."""
+ if not isinstance(result, tuple):
+ return Error('MissingNewline', 'Should have newline after ;', pos, LINES)
+
+
+@rule(-(xsp + keep(';')) + shouldBeNewline + xsp)
+def shouldBeSemicolonAndNewline(result, pos):
+ """A place where there should a semicolon, but compiler-wise it is optional."""
+ errors = []
+ if result:
+ if isinstance(result, Error):
+ errors.append(result)
+ result = None
+ else:
+ errors.extend([e for e in result if isinstance(e, Error)])
+
+ if not result:
+ errors.append(Error('MissingSemicolon', 'Expected a semicolon', pos, LINES))
+
+ return errors or None
+
+
+@rule(methodSignature + shouldBeSemicolonAndNewline + codeBlock)
+def method(value):
+ """A method."""
+ return stringsAndErrors(value)
+
+
+@rule(First('@class ', '@protocol ') + xsp + anyIdentifier + xsp + ';')
+def forwardDeclaration(value):
+ """A forward declaration of a class."""
+ return justErrors(value)
+
+
+filePart.set(inclusion | interface | implementation | cppClass | namespace | '\n' | ' ' | method | methodDeclaration |
+ protocolDeclaration | forwardDeclaration | string | objcString | codeBlock | anyPreprocessor | AnyChar())
+
+
+@rule(+filePart)
+def entireFile(value):
+ """The entire file."""
+ return stringsAndErrors(value)
View
129 src/ocstyle/rules_test.py
@@ -0,0 +1,129 @@
+# Copyright 2012 The ocstyle Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for the Objective C style rules."""
+
+from parcon import Exact
+
+import unittest
+
+from ocstyle import rules
+
+
+
+class RulesTest(unittest.TestCase):
+ """Tests for Objective C rules."""
+
+ def assertMatches(self, rule, text):
+ """Check if the given rule matches the given text."""
+ Exact(rule).parse_string(text)
+
+
+ def testDirective(self):
+ """Test for preprocessor directives."""
+ self.assertMatches(rules.directive, '#ifdef XYZ')
+ self.assertMatches(rules.directive, '#define XYZ\\\n"a string with a backslash \\t in it"')
+
+
+ def testObjCType(self):
+ """Test for Objective C types."""
+ self.assertMatches(rules.objcType, 'NSString *')
+ self.assertMatches(rules.objcType, 'NSString*')
+ self.assertMatches(rules.objcType, 'NSString **')
+ self.assertMatches(rules.objcType, 'id')
+ self.assertMatches(rules.objcType, 'void')
+ self.assertMatches(rules.objcType, 'signed long')
+ self.assertMatches(rules.objcType, 'short int')
+ self.assertMatches(rules.objcType, 'unsigned long long')
+ self.assertMatches(rules.objcType, 'unsigned long long int')
+ self.assertMatches(rules.objcType, 'void(^)()')
+ self.assertMatches(rules.objcType, 'const float(^)(id)')
+ self.assertMatches(rules.objcType, 'NSArray *(^)(id, int)')
+ self.assertMatches(rules.objcType, 'id<LETRenderCommandDelegate>')
+ self.assertMatches(rules.objcType, 'NSObject<LETRenderCommandDelegate> *')
+ self.assertMatches(rules.objcType, 'id<LETRenderCommandDelegate, NSURLConnectionDelegate>')
+ self.assertMatches(rules.objcType, 'std::vector<LETRenderCommand *>::const_reverse_iterator')
+ self.assertMatches(rules.objcType, 'std::vector<std::list<char>, 5>::const_reverse_iterator<potato>')
+
+
+ def testSelectorPart(self):
+ """Test for selector part."""
+ self.assertMatches(rules.selectorPart, 'initWithKey:(NSString *)key')
+
+
+ def testMethodDeclaration(self):
+ """Test for the method declaration rule."""
+ self.assertMatches(rules.methodDeclaration, '- (BOOL)isSet;')
+ self.assertMatches(rules.methodDeclaration, '- (id)initWithKey: (NSString *)key;')
+ self.assertMatches(rules.methodDeclaration, '- (NSArray *)loadWithManager:(id)manager message:(id)message;')
+ self.assertMatches(rules.methodDeclaration, '- (NSArray *)loadWithManager:(id)manager\n message:(id)message;')
+ self.assertMatches(rules.methodDeclaration, '- (void)successfullyDeletedIndexWithId:(int64_t)indexId;')
+
+
+ def testPropertyDeclaration(self):
+ """Test for property declarations."""
+ self.assertMatches(rules.propertyDeclaration, '@property BOOL shouldForceFrame;')
+ self.assertMatches(rules.propertyDeclaration, '@property (readonly) SCNavigationBar * fakeBackground;')
+ self.assertMatches(rules.propertyDeclaration, '@property(nonatomic, getter=isEnabled) BOOL enabled;')
+ self.assertMatches(rules.propertyDeclaration, '@property (retain) IBOutlet UIImageView *backArrow;')
+ self.assertMatches(rules.propertyDeclaration, '@property (copy) void (^onContentPresented)(CUTableViewItem *item);')
+
+
+ def testEmptyProtocol(self):
+ """Test an empty protocol."""
+ self.assertMatches(rules.protocolDeclaration, '@protocol TheName\n@end')
+
+
+ def testSimpleType(self):
+ """Tests simple types."""
+ self.assertMatches(rules.simpleType, 'long')
+ self.assertMatches(rules.simpleType, 'long long')
+ self.assertMatches(rules.simpleType, 'long long int')
+ self.assertMatches(rules.simpleType, 'unsigned long long int')
+ self.assertMatches(rules.simpleType + rules.sp(1) + rules.ivarName, 'long long _expectedLength')
+
+
+ def testInstanceVariable(self):
+ """Test for an instance variable."""
+ self.assertMatches(rules.ivar, 'long long _expectedLength;')
+ self.assertMatches(rules.ivar, 'void (^_onContentPresented)(CUTableViewItem *);')
+ self.assertMatches(rules.ivar, 'NSMutableSet *_active[__CCRequestPriorityImmediately + 1];')
+ self.assertMatches(rules.ivar, 'unsigned long long int _expectedLength;')
+
+
+ def testInterfaceDeclaration(self):
+ """Test for interface declarations."""
+ self.assertMatches(rules.interface, '@interface ABC\n<DEF>\n@end')
+ self.assertMatches(rules.interface, '@interface LETImageAttributes : NSObject<NSCopying>\n@end')
+
+
+ def testMethod(self):
+ """Test for method."""
+ self.assertMatches(rules.method, '''
++ (NSString *)serverAddressWithSubdomain:(NSString *)subdomain;
+{
+ return FORMAT(@"%@://%@", [self serverProtocol], [self serverHostWithSubdomain:subdomain]);
+}
+ '''.strip())
+
+
+ def testMacroCall(self):
+ """Test for macro call."""
+ self.assertMatches(rules.macroCall, 'CCBlockProperty(BlockURLHandler, withCheckBlock, (URLHandlingBlock));')
+
+
+ def testNamespace(self):
+ """Test for namespace."""
+ self.assertMatches(rules.namespace, 'namespace com {}')
+ self.assertMatches(rules.namespace, 'namespace com\n{\n}')
View
126 src/ocstyle/testdata/Parsing.h
@@ -0,0 +1,126 @@
+//
+// Parsing.h
+// ocstyle
+//
+// Created by Robby Walker on 9/30/12.
+// Copyright (c) 2013 The ocstyle Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// EXPECT+1:MissingSpace
+#import"Test.h"
+// EXPECT+1:ExtraSpace
+#import "Test2.h"
+
+#define SIMPLE_TYPED_PROPERTY(__name__, __camelName__, __propId__, __type__) \
+- (__type__)__name__;
+
+@class ForwardClass ; // EXPECT: ExtraSpace
+@protocol ForwardProtocol; // EXPECT: ExtraSpace
+
+@interface UIStateValue : NSObject { // EXPECT: ExpectedInterfaceDocInHeader
+@private
+ NSString* _key; // EXPECT: MissingSpace, ExtraSpace
+ volatile int _value; // EXPECT: ExtraSpace
+ unsigned long long _specialSize;
+ long long _specialSize; // EXPECT: ExtraSpace
+
+#ifdef _STATS
+ id<NSURLConnectionDelegate,UITableViewDelegate> _drawCount; // EXPECT: MissingSpace
+ NSObject <NSURLConnectionDelegate> *_delegate2; // EXPECT: ExtraSpace
+#endif
+}
+
+- (id)_initWithKey: (NSString *)key; // EXPECT: ExtraSpace, PrivateSelectorInHeader
+
+- (BOOL)isSet;
+
+-(void)set; // EXPECT: MissingSpace
+
+@property BOOL shouldForceFrame; // EXPECT: ExpectedPropertyDocInHeader
+
+@property (readonly) SCNavigationBar*fakeBackground; // EXPECT: MissingSpace, ExpectedPropertyDocInHeader
+@property (readonly)SCNavigationBar *fakeBackground; // EXPECT: MissingSpace, ExpectedPropertyDocInHeader
+@property(readonly) SCNavigationBar *fakeBackground; // EXPECT: MissingSpace, ExpectedPropertyDocInHeader
+@property (readonly) SCNavigationBar *fakeBackground ; // EXPECT: ExtraSpace, ExpectedPropertyDocInHeader
+
+// This line is just the right size.....................................................................................
+// EXPECT+1:LineTooLong
+// This line is too long.................................................................................................
+
+/**
+ * This property has documentation.
+ */
+@property (readonly) SCNavigationBar * fakeBackground; // EXPECT: ExtraSpace
+
+@property (nonatomic, getter= isEnabled) BOOL enabled; // EXPECT: MissingSpace, ExpectedPropertyDocInHeader
+@property (nonatomic, getter =isEnabled) BOOL enabled; // EXPECT: MissingSpace, ExpectedPropertyDocInHeader
+@property (nonatomic, getter = isEnabled) BOOL enabled; // EXPECT: ExpectedPropertyDocInHeader
+
+/**
+* If we want "sometimes" handling of an url
+*/
+CCBlockProperty(BlockURLHandler, withCheckBlock, (URLHandlingBlock));
+
+@end
+
+
+// EXPECT+1: ExpectedProtocolDocInHeader
+@protocol PersistentCacheable
+
+@property (nonatomic, retain) NSDate *modifiedDate; // EXPECT: ExtraSpace, ExpectedPropertyDocInHeader
+
+@optional
+
+- (NSArray *)loadWithManager:(id)manager message:(id)message;
+
+@end
+
+
+/**
+ * Manages application states that, once set, stay set. Callbacks can be registered for state changes.
+ */
+@interface UIState : NSObject
+
++ (void)waitForAny:(NSArray *)keys andExecute:(void(^)())block;
+
+- (uint8_t /* CTLineBreakMode */)lineBreakModeForTitle; // This caused a parse error before.
+
+@end
+
+
+/**
+ * This list of protocols is ok.
+ */
+@interface UIState : NSObject
+<ProtocolA,
+ProtocolB>
+{
+ void (^_tryAgain)(void);
+ NSString *(^_getAString)(void);
+}
+
+@end
+
+class NativeContext : public CCNativeCountInstances {
+ id *_resolvedValues;
+};
+
+namespace com
+{
+ namespace blah
+ {
+ class NativeFloat;
+ }
+}
View
168 src/ocstyle/testdata/Parsing.m
@@ -0,0 +1,168 @@
+//
+// Parsing.m
+// ocstyle
+//
+// Created by Robby Walker on 9/30/12.
+// Copyright (c) 2012 The ocstyle Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+
+// OK to not document in .m file.
+@interface UIStateValue : NSObject {
+}
+
+// OK to not document in .m file.
+@property (nonatomic, retain) NSDate *modifiedDate;
+
+@end
+
+
+@implementation UIStateValue {
+ int _x;
+ double _y;
+}
+
++ (NSString *)_serverAddressWithSubdomain:(NSString *)subdomain;
+{
+ // Private selectors ok in implementation files.
+ goto SomeLabel;
+}
+
+
++ (NSString *)serverAddressWithSubdomain:(NSString *)subdomain;
+{
+ return FORMAT(@"%@://%@", [self serverProtocol], [self serverHostWithSubdomain:subdomain]);
+}
+
+
+ // EXPECT+1: MissingSemicolon, MissingNewline
++ (void)badFormat:(NSString *)subdomain{
+}
+
++ (void)badFormat:(NSString *)subdomain;
+ { // EXPECT: ExtraSpace
+}
+
+
+// EXPECT+1: ExtraSpace
++ (void)badFormat:(NSString *)subdomain ;
+{
+}
+
+
+// EXPECT+1: MissingSemicolon, MissingNewline
++ (void)badFormat:(NSString *)subdomain {
+}
+
+
+// EXPECT+1: MissingNewline
++ (void)badFormat:(NSString *)subdomain; {
+}
+
+
+// TODO(robbyw): Need a case for comment after ; but before {
+
+
+/* fails to parse this part - URL_HANDLER(Hs) */{
+ return FORMAT(@"%@://%@", [self serverProtocol], [self serverHostWithSubdomain:subdomain]);
+}
+
+
++ (void)someMessage:(NSString *)subdomain;
+{
+ int ok;
+ NSString *BadName; // EXPECT: BadLocalVariableName
+ const NSString *okSpace = @"1";
+ NSString * extraSpace; // EXPECT: ExtraSpace
+ static volatile NSString*missingSpace = @"1"; // EXPECT: MissingSpace
+ NSString*missingSpace2 = @"1"; // EXPECT: MissingSpace
+ NSString *missingSpace3= @"1"; // EXPECT: MissingSpace
+ NSString *missingSpace4 =@"1"; // EXPECT: MissingSpace
+
+ std::vector<RenderCommand *>::const_reverse_iterator iter = _renderGroupInfo->get_zCommands()->rbegin();
+
+ delete _local;
+
+ int x =
+ 5;
+
+ if (x > 10) {
+ return _somePrivateVariable;
+ } else {
+ return self.width * page;
+ }
+
+ // TODO(robbyw): NSString *missingSpace = @"1",*another = @"2"; - EXPECT: MissingSpace
+}
+
+
+- (NSURL *)mapsUrl;
+{
+ std::vector<NativeResponse *> reverse;
+ std::vector<NativeResponse *>reverse; // EXPECT: MissingSpace
+
+ if(x) { // EXPECT: MissingSpace
+ }
+
+ if (x){ // EXPECT: MissingSpace
+ }
+
+ if (x) {
+ }
+
+ if ( x) { // EXPECT: ExtraSpace
+ }
+
+ if (x ) { // EXPECT: ExtraSpace
+ }
+
+ for(x) { // EXPECT: MissingSpace
+ }
+
+ for (x){ // EXPECT: MissingSpace
+ }
+
+ for (x) {
+ }
+
+ for ( x) { // EXPECT: ExtraSpace
+ }
+
+ for (x ) { // EXPECT: ExtraSpace
+ }
+
+ while(x) { // EXPECT: MissingSpace
+ }
+
+ while (x){ // EXPECT: MissingSpace
+ }
+
+ while (x) {
+ }
+
+ while ( x) { // EXPECT: ExtraSpace
+ }
+
+ while (x ) { // EXPECT: ExtraSpace
+ }
+
+ if (x)
+ return @"";
+ NSString *mapString = FORMAT(@"http://%@/maps?q=%@", [Platform mapsDomain], [self urlEncodedString]);
+ return [NSURL URLWithString:mapString];
+}
+
+
+@end

0 comments on commit f9c2acf

Please sign in to comment.
Something went wrong with that request. Please try again.